diff --git a/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/execution_pipeline.ts b/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/execution_pipeline.ts index a5470e8916329..e35cce832cc45 100644 --- a/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/execution_pipeline.ts +++ b/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/execution_pipeline.ts @@ -35,7 +35,8 @@ export class RuleExecutionPipeline implements RuleExecutionPipelineContract { constructor( @inject(LoggerServiceToken) private readonly logger: LoggerServiceContract, @multiInject(RuleExecutionStepsToken) private readonly steps: RuleExecutionStep[], - @inject(RuleExecutionMiddlewaresToken) private readonly middlewares: RuleExecutionMiddleware[] + @multiInject(RuleExecutionMiddlewaresToken) + private readonly middlewares: RuleExecutionMiddleware[] ) {} public async execute(input: RuleExecutionInput): Promise { diff --git a/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/middleware/apm_middleware.test.ts b/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/middleware/apm_middleware.test.ts new file mode 100644 index 0000000000000..27d088cfff92b --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/middleware/apm_middleware.test.ts @@ -0,0 +1,40 @@ +/* + * 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 { withSpan } from '@kbn/apm-utils'; +import { ApmMiddleware } from './apm_middleware'; +import { createRuleExecutionMiddlewareContext } from './test_utils'; + +jest.mock('@kbn/apm-utils', () => ({ + withSpan: jest.fn((_opts: unknown, cb: () => Promise) => cb()), +})); + +const withSpanMock = withSpan as jest.MockedFunction; + +describe('ApmSpanMiddleware', () => { + const middleware = new ApmMiddleware(); + + it('wraps next() in withSpan and returns result on success', async () => { + const expectedResult = { type: 'continue' }; + const next = jest.fn().mockResolvedValue(expectedResult); + const context = createRuleExecutionMiddlewareContext(); + + const result = await middleware.execute(context, next); + + expect(result).toEqual(expectedResult); + expect(next).toHaveBeenCalledTimes(1); + expect(withSpanMock).toHaveBeenCalledTimes(1); + expect(withSpanMock).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'rule_executor:test_step', + type: 'rule_executor', + labels: { plugin: 'alerting_v2' }, + }), + expect.any(Function) + ); + }); +}); diff --git a/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/middleware/apm_middleware.ts b/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/middleware/apm_middleware.ts new file mode 100644 index 0000000000000..882502c986b81 --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/middleware/apm_middleware.ts @@ -0,0 +1,32 @@ +/* + * 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 { withSpan } from '@kbn/apm-utils'; +import type { RuleExecutionMiddlewareContext, RuleExecutionMiddleware } from './types'; +import type { RuleStepOutput } from '../types'; +import { APP_ID } from '../../constants'; + +/** + * Middleware that wraps each step execution in an APM span for tracing. + */ +export class ApmMiddleware implements RuleExecutionMiddleware { + public readonly name = 'apm_span'; + + public async execute( + ctx: RuleExecutionMiddlewareContext, + next: () => Promise + ): Promise { + return withSpan( + { + name: `rule_executor:${ctx.step.name}`, + type: 'rule_executor', + labels: { plugin: APP_ID }, + }, + () => next() + ); + } +} diff --git a/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/middleware/index.ts b/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/middleware/index.ts index 4153f823e8c80..cb63c06efb7c8 100644 --- a/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/middleware/index.ts +++ b/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/middleware/index.ts @@ -6,4 +6,5 @@ */ export type { RuleExecutionMiddleware, RuleExecutionMiddlewareContext } from './types'; +export { ApmMiddleware } from './apm_middleware'; export { ErrorHandlingMiddleware } from './error_handling_middleware'; diff --git a/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/tokens.ts b/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/tokens.ts index d6301498ceb03..51117b6078b45 100644 --- a/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/tokens.ts +++ b/x-pack/platform/plugins/shared/alerting_v2/server/lib/rule_executor/tokens.ts @@ -18,9 +18,8 @@ export const RuleExecutionStepsToken = Symbol.for( ) as ServiceIdentifier; /** - * DI token for the array of step middleware. - * Middleware are executed in order (first middleware is outermost). + * Token for multi-injecting the ordered execution middlewares. */ export const RuleExecutionMiddlewaresToken = Symbol.for( 'alerting_v2.RuleExecutionMiddlewares' -) as ServiceIdentifier; +) as ServiceIdentifier; diff --git a/x-pack/platform/plugins/shared/alerting_v2/server/setup/bind_rule_executor.ts b/x-pack/platform/plugins/shared/alerting_v2/server/setup/bind_rule_executor.ts index dc1fdb9d2131f..033b1a11de764 100644 --- a/x-pack/platform/plugins/shared/alerting_v2/server/setup/bind_rule_executor.ts +++ b/x-pack/platform/plugins/shared/alerting_v2/server/setup/bind_rule_executor.ts @@ -19,7 +19,7 @@ import { CreateAlertEventsStep, CreateRecoveryEventsStep, } from '../lib/rule_executor/steps'; -import { ErrorHandlingMiddleware } from '../lib/rule_executor/middleware'; +import { ApmMiddleware, ErrorHandlingMiddleware } from '../lib/rule_executor/middleware'; import { DirectorStep } from '../lib/rule_executor/steps/director_step'; import { StoreAlertEventsStep } from '../lib/rule_executor/steps/store_alert_events'; @@ -27,17 +27,15 @@ export const bindRuleExecutionServices = ({ bind }: ContainerModuleLoadOptions) /** * Middlewares */ + bind(ApmMiddleware).toSelf().inSingletonScope(); bind(ErrorHandlingMiddleware).toSelf().inSingletonScope(); /** - * Middleware list + * Middleware list via multi-injection. + * Binding order defines execution order. */ - bind(RuleExecutionMiddlewaresToken) - .toDynamicValue(({ get }) => [ - // Add more middleware here as needed - get(ErrorHandlingMiddleware), - ]) - .inSingletonScope(); + bind(RuleExecutionMiddlewaresToken).to(ApmMiddleware).inSingletonScope(); + bind(RuleExecutionMiddlewaresToken).to(ErrorHandlingMiddleware).inSingletonScope(); /** * Rule execution steps via multi-injection.