diff --git a/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/apm_error/fetch_apm_error_context.ts b/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/apm_error/fetch_apm_error_context.ts index d2a305d5adb37..d9092a8fc282d 100644 --- a/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/apm_error/fetch_apm_error_context.ts +++ b/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/apm_error/fetch_apm_error_context.ts @@ -161,7 +161,7 @@ export async function fetchApmErrorContext({ }, logger, categoryCount: 10, - terms: { 'trace.id': traceId }, + fields: ['trace.id'], }); return logCategories?.categories; diff --git a/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_log_categories/handler.ts b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_log_categories/handler.ts index 2813a16f996e5..5f69ac60421c7 100644 --- a/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_log_categories/handler.ts +++ b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_log_categories/handler.ts @@ -14,8 +14,7 @@ import type { import { getLogsIndices } from '../../utils/get_logs_indices'; import { getTypedSearch } from '../../utils/get_typed_search'; import { getTotalHits } from '../../utils/get_total_hits'; -import { getShouldMatchOrNotExistFilter } from '../../utils/get_should_match_or_not_exist_filter'; -import { timeRangeFilter } from '../../utils/dsl_filters'; +import { timeRangeFilter, kqlFilter } from '../../utils/dsl_filters'; import { parseDatemath } from '../../utils/time'; export async function getToolHandler({ @@ -25,7 +24,8 @@ export async function getToolHandler({ index, start, end, - terms, + kqlFilter: kuery, + fields, }: { core: CoreSetup< ObservabilityAgentBuilderPluginStartDependencies, @@ -36,15 +36,16 @@ export async function getToolHandler({ index?: string; start: string; end: string; - terms?: Record; + kqlFilter?: string; + fields: string[]; }) { const logsIndices = index?.split(',') ?? (await getLogsIndices({ core, logger })); const boolFilters = [ ...timeRangeFilter('@timestamp', { - ...getShouldMatchOrNotExistFilter(terms), start: parseDatemath(start), end: parseDatemath(end, { roundUp: true }), }), + ...kqlFilter(kuery), { exists: { field: 'message' } }, ]; @@ -66,7 +67,7 @@ export async function getToolHandler({ boolQuery: { filter: boolFilters, must_not: lowSeverityLogLevels }, logger, categoryCount: 20, - terms, + fields, }), getFilteredLogCategories({ esClient, @@ -74,7 +75,7 @@ export async function getToolHandler({ boolQuery: { filter: boolFilters, must: lowSeverityLogLevels }, logger, categoryCount: 10, - terms, + fields, }), ]); @@ -87,14 +88,14 @@ export async function getFilteredLogCategories({ boolQuery, logger, categoryCount, - terms, + fields, }: { esClient: IScopedClusterClient; logsIndices: string[]; boolQuery: QueryDslBoolQuery; logger: Logger; categoryCount: number; - terms: Record | undefined; + fields: string[]; }) { const search = getTypedSearch(esClient.asCurrentUser); @@ -143,7 +144,7 @@ export async function getFilteredLogCategories({ top_hits: { size: 1, _source: false, - fields: ['message', '@timestamp', ...Object.keys(terms ?? {})], + fields: ['message', '@timestamp', ...fields], sort: { '@timestamp': { order: 'desc' }, }, diff --git a/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_log_categories/tool.ts b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_log_categories/tool.ts index cd4753f639c20..8002e8911b5aa 100644 --- a/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_log_categories/tool.ts +++ b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_log_categories/tool.ts @@ -18,6 +18,7 @@ import { timeRangeSchemaOptional, indexDescription } from '../../utils/tool_sche import { getAgentBuilderResourceAvailability } from '../../utils/get_agent_builder_resource_availability'; import type { getFilteredLogCategories } from './handler'; import { getToolHandler } from './handler'; +import { OBSERVABILITY_GET_CORRELATED_LOGS_TOOL_ID } from '../get_correlated_logs/tool'; export interface GetLogCategoriesToolResult { type: ToolResultType.other; @@ -37,11 +38,17 @@ export const OBSERVABILITY_GET_LOG_CATEGORIES_TOOL_ID = 'observability.get_log_c const getLogsSchema = z.object({ ...timeRangeSchemaOptional(DEFAULT_TIME_RANGE), index: z.string().describe(indexDescription).optional(), - terms: z - .record(z.string(), z.string()) + kqlFilter: z + .string() .optional() .describe( - 'Optional field filters to narrow down results. Each key-value pair filters logs where the field exactly matches the value. Example: { "service.name": "payment", "host.name": "web-server-01" }. Multiple filters are combined with AND logic.' + 'A KQL query to filter logs. Examples: service.name:"payment", host.name:"web-server-01", service.name:"payment" AND log.level:error' + ), + fields: z + .array(z.string()) + .optional() + .describe( + 'Additional fields to return for each log sample. "message" and "@timestamp" are always included. Example: ["service.name", "host.name"]' ), }); @@ -69,9 +76,15 @@ When to use: How it works: Groups similar log messages together using pattern recognition, returning representative categories with counts. +After using this tool: +- For high-count error categories, use \`${OBSERVABILITY_GET_CORRELATED_LOGS_TOOL_ID}\` to trace the sequence of events leading to those errors +- Compare error patterns across services - the origin service often has different error types than affected downstream services (e.g., "constraint violation" vs "connection timeout") +- Patterns like "timeout", "exhausted", "capacity", "limit reached" are often SYMPTOMS - look for what's causing the resource pressure +- If you see resource lifecycle logs (acquire/release, open/close), check if counts match - mismatches can indicate leaks + Do NOT use for: -- Understanding the sequence of events for a specific error (use get_correlated_logs) -- Investigating a specific incident in detail (use get_correlated_logs) +- Understanding the sequence of events for a specific error (use ${OBSERVABILITY_GET_CORRELATED_LOGS_TOOL_ID}) +- Investigating a specific incident in detail (use ${OBSERVABILITY_GET_CORRELATED_LOGS_TOOL_ID}) - Analyzing changes in log volume over time (use run_log_rate_analysis)`, schema: getLogsSchema, tags: ['observability', 'logs'], @@ -86,7 +99,8 @@ Do NOT use for: index, start = DEFAULT_TIME_RANGE.start, end = DEFAULT_TIME_RANGE.end, - terms, + kqlFilter, + fields = [], } = toolParams; try { @@ -97,7 +111,8 @@ Do NOT use for: index, start, end, - terms, + kqlFilter, + fields, }); return { diff --git a/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/tools/get_log_categories.spec.ts b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/tools/get_log_categories.spec.ts index e5f1db0dff745..7b4c427d9fdfd 100644 --- a/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/tools/get_log_categories.spec.ts +++ b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/tools/get_log_categories.spec.ts @@ -10,7 +10,6 @@ import { timerange } from '@kbn/synthtrace-client'; import { type LogsSynthtraceEsClient, generateLogCategoriesData } from '@kbn/synthtrace'; import { OBSERVABILITY_GET_LOG_CATEGORIES_TOOL_ID } from '@kbn/observability-agent-builder-plugin/server/tools'; import type { GetLogCategoriesToolResult } from '@kbn/observability-agent-builder-plugin/server/tools/get_log_categories/tool'; -import { first } from 'lodash'; import type { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context'; import { createAgentBuilderApiClient } from '../utils/agent_builder_client'; @@ -53,7 +52,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { params: { start: START, end: END, - terms: { 'service.name': SERVICE_NAME }, + kqlFilter: `service.name:"${SERVICE_NAME}"`, }, }); @@ -81,7 +80,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { params: { start: START, end: END, - terms: { 'service.name': SERVICE_NAME }, + kqlFilter: `service.name:"${SERVICE_NAME}"`, }, }); @@ -112,15 +111,13 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { expect(category).to.have.property('count'); expect(category.count).to.be.greaterThan(0); - // Sample should include requested fields + // Sample should include core fields expect(category.sample).to.have.property('message'); expect(category.sample).to.have.property('@timestamp'); - expect(category.sample).to.have.property('service.name'); - expect(first(category.sample['service.name'])).to.be(SERVICE_NAME); }); }); - it('works without terms filter', async () => { + it('works without kqlFilter', async () => { const results = await agentBuilderApiClient.executeTool({ id: OBSERVABILITY_GET_LOG_CATEGORIES_TOOL_ID, params: {