diff --git a/x-pack/plugins/fleet/server/collectors/agent_logs.ts b/x-pack/plugins/fleet/server/collectors/agent_logs.ts new file mode 100644 index 0000000000000..562a217c2bd19 --- /dev/null +++ b/x-pack/plugins/fleet/server/collectors/agent_logs.ts @@ -0,0 +1,89 @@ +/* + * 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 type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; + +import { appContextService } from '../services'; + +export interface AgentLogsData { + agent_logs_top_errors: string[]; + fleet_server_logs_top_errors: string[]; +} + +const DEFAULT_LOGS_DATA = { + agent_logs_top_errors: [], + fleet_server_logs_top_errors: [], +}; + +export async function getAgentLogsTopErrors( + esClient?: ElasticsearchClient +): Promise { + if (!esClient) { + return DEFAULT_LOGS_DATA; + } + try { + const queryTopMessages = (index: string) => + esClient.search({ + index, + size: 0, + query: { + bool: { + filter: [ + { + term: { + 'log.level': 'error', + }, + }, + { + range: { + '@timestamp': { + gte: 'now-1h', + }, + }, + }, + ], + }, + }, + aggs: { + message_sample: { + sampler: { + shard_size: 200, + }, + aggs: { + categories: { + categorize_text: { + field: 'message', + size: 10, + }, + }, + }, + }, + }, + }); + + const transformBuckets = (resp: any) => + ((resp?.aggregations?.message_sample as any)?.categories?.buckets ?? []) + .slice(0, 3) + .map((bucket: any) => bucket.key); + + const agentResponse = await queryTopMessages('logs-elastic_agent-*'); + + const fleetServerResponse = await queryTopMessages('logs-elastic_agent.fleet_server-*'); + + return { + agent_logs_top_errors: transformBuckets(agentResponse), + fleet_server_logs_top_errors: transformBuckets(fleetServerResponse), + }; + } catch (error) { + if (error.statusCode === 404) { + appContextService.getLogger().debug('Index pattern logs-elastic_agent* does not exist yet.'); + } else { + throw error; + } + return DEFAULT_LOGS_DATA; + } +} diff --git a/x-pack/plugins/fleet/server/collectors/register.ts b/x-pack/plugins/fleet/server/collectors/register.ts index 2892de0685e2f..31513eb03a070 100644 --- a/x-pack/plugins/fleet/server/collectors/register.ts +++ b/x-pack/plugins/fleet/server/collectors/register.ts @@ -19,6 +19,7 @@ import type { PackageUsage } from './package_collectors'; import { getFleetServerUsage, getFleetServerConfig } from './fleet_server_collector'; import type { FleetServerUsage } from './fleet_server_collector'; import { getAgentPoliciesUsage } from './agent_policies'; +import { getAgentLogsTopErrors } from './agent_logs'; export interface Usage { agents_enabled: boolean; @@ -44,6 +45,7 @@ export const fetchFleetUsage = async ( ...(await getAgentData(esClient, abortController)), fleet_server_config: await getFleetServerConfig(soClient), agent_policies: await getAgentPoliciesUsage(esClient, abortController), + ...(await getAgentLogsTopErrors(esClient)), }; return usage; }; diff --git a/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts b/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts index 3f85a8533f4f4..474c37025f65a 100644 --- a/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts @@ -179,6 +179,32 @@ describe('fleet usage telemetry', () => { refresh: 'wait_for', }); + await esClient.create({ + index: 'logs-elastic_agent-default', + id: 'log1', + body: { + log: { + level: 'error', + }, + '@timestamp': new Date().toISOString(), + message: 'stderr panic close of closed channel', + }, + refresh: 'wait_for', + }); + + await esClient.create({ + index: 'logs-elastic_agent.fleet_server-default', + id: 'log2', + body: { + log: { + level: 'error', + }, + '@timestamp': new Date().toISOString(), + message: 'failed to unenroll offline agents', + }, + refresh: 'wait_for', + }); + const soClient = kbnServer.coreStart.savedObjects.createInternalRepository(); await soClient.create('ingest-package-policies', { name: 'fleet_server-1', @@ -255,6 +281,8 @@ describe('fleet usage telemetry', () => { ], }, agent_policies: { count: 3, output_types: ['elasticsearch'] }, + agent_logs_top_errors: ['stderr panic close of closed channel'], + fleet_server_logs_top_errors: ['failed to unenroll offline agents'], }) ); }); diff --git a/x-pack/plugins/fleet/server/services/telemetry/fleet_usages_schema.ts b/x-pack/plugins/fleet/server/services/telemetry/fleet_usages_schema.ts index 9eeb867bd9b91..3e3bdc9d3b243 100644 --- a/x-pack/plugins/fleet/server/services/telemetry/fleet_usages_schema.ts +++ b/x-pack/plugins/fleet/server/services/telemetry/fleet_usages_schema.ts @@ -165,4 +165,18 @@ export const fleetUsagesSchema: RootSchema = { }, }, }, + agent_logs_top_errors: { + type: 'array', + items: { + type: 'text', + _meta: { description: 'Top messages from agent error logs' }, + }, + }, + fleet_server_logs_top_errors: { + type: 'array', + items: { + type: 'text', + _meta: { description: 'Top messages from fleet server error logs' }, + }, + }, };