From 212b172b678b6f648d370a164ca1a1d70a7dda01 Mon Sep 17 00:00:00 2001 From: jpdjere Date: Thu, 24 Oct 2024 17:55:26 -0300 Subject: [PATCH 1/2] Add sync security spans --- .../create_upgradeable_rules_payload.ts | 176 ++++++++++-------- .../get_upgradeable_rules.ts | 105 ++++++----- .../perform_rule_upgrade_route.ts | 4 + .../server/utils/with_security_span.ts | 18 ++ 4 files changed, 172 insertions(+), 131 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts index 97e587646e524..956c6a1e6e2bd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts @@ -4,7 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import type { Transaction } from 'elastic-apm-node'; import { pickBy } from 'lodash'; +import { withSyncSecuritySpan } from '../../../../../utils/with_security_span'; import type { PromisePoolError } from '../../../../../utils/promise_pool'; import { PickVersionValuesEnum, @@ -25,6 +27,7 @@ import { getValueForField } from './get_value_for_field'; interface CreateModifiedPrebuiltRuleAssetsProps { upgradeableRules: RuleTriad[]; requestBody: PerformRuleUpgradeRequestBody; + transaction: Transaction; } interface ProcessedRules { @@ -35,78 +38,85 @@ interface ProcessedRules { export const createModifiedPrebuiltRuleAssets = ({ upgradeableRules, requestBody, + transaction, }: CreateModifiedPrebuiltRuleAssetsProps) => { - const { pick_version: globalPickVersion = PickVersionValuesEnum.MERGED, mode } = requestBody; - - const { modifiedPrebuiltRuleAssets, processingErrors } = upgradeableRules.reduce( - (processedRules, upgradeableRule) => { - const targetRuleType = upgradeableRule.target.type; - const ruleId = upgradeableRule.target.rule_id; - const fieldNames = FIELD_NAMES_BY_RULE_TYPE_MAP.get(targetRuleType); - - try { - if (fieldNames === undefined) { - throw new Error(`Unexpected rule type: ${targetRuleType}`); - } - - const { current, target } = upgradeableRule; - if (current.type !== target.type) { - assertPickVersionIsTarget({ ruleId, requestBody }); - } - - const calculatedRuleDiff = calculateRuleFieldsDiff({ - base_version: upgradeableRule.base - ? convertRuleToDiffable(convertPrebuiltRuleAssetToRuleResponse(upgradeableRule.base)) - : MissingVersion, - current_version: convertRuleToDiffable(upgradeableRule.current), - target_version: convertRuleToDiffable( - convertPrebuiltRuleAssetToRuleResponse(upgradeableRule.target) - ), - }) as AllFieldsDiff; - - if (mode === 'ALL_RULES' && globalPickVersion === 'MERGED') { - const fieldsWithConflicts = Object.keys(getFieldsDiffConflicts(calculatedRuleDiff)); - if (fieldsWithConflicts.length > 0) { - // If the mode is ALL_RULES, no fields can be overriden to any other pick_version - // than "MERGED", so throw an error for the fields that have conflicts. - throw new Error( - `Merge conflicts found in rule '${ruleId}' for fields: ${fieldsWithConflicts.join( - ', ' - )}. Please resolve the conflict manually or choose another value for 'pick_version'` - ); + return withSyncSecuritySpan(transaction, 'createModifiedPrebuiltRuleAssets', () => { + const { pick_version: globalPickVersion = PickVersionValuesEnum.MERGED, mode } = requestBody; + + const { modifiedPrebuiltRuleAssets, processingErrors } = + upgradeableRules.reduce( + (processedRules, upgradeableRule) => { + const targetRuleType = upgradeableRule.target.type; + const ruleId = upgradeableRule.target.rule_id; + const fieldNames = FIELD_NAMES_BY_RULE_TYPE_MAP.get(targetRuleType); + + try { + if (fieldNames === undefined) { + throw new Error(`Unexpected rule type: ${targetRuleType}`); + } + + const { current, target } = upgradeableRule; + if (current.type !== target.type) { + assertPickVersionIsTarget({ ruleId, requestBody }); + } + + const calculatedRuleDiff = calculateRuleFieldsDiff({ + base_version: upgradeableRule.base + ? convertRuleToDiffable( + convertPrebuiltRuleAssetToRuleResponse(upgradeableRule.base) + ) + : MissingVersion, + current_version: convertRuleToDiffable(upgradeableRule.current), + target_version: convertRuleToDiffable( + convertPrebuiltRuleAssetToRuleResponse(upgradeableRule.target) + ), + }) as AllFieldsDiff; + + if (mode === 'ALL_RULES' && globalPickVersion === 'MERGED') { + const fieldsWithConflicts = Object.keys(getFieldsDiffConflicts(calculatedRuleDiff)); + if (fieldsWithConflicts.length > 0) { + // If the mode is ALL_RULES, no fields can be overriden to any other pick_version + // than "MERGED", so throw an error for the fields that have conflicts. + throw new Error( + `Merge conflicts found in rule '${ruleId}' for fields: ${fieldsWithConflicts.join( + ', ' + )}. Please resolve the conflict manually or choose another value for 'pick_version'` + ); + } + } + + const modifiedPrebuiltRuleAsset = createModifiedPrebuiltRuleAsset({ + upgradeableRule, + fieldNames, + requestBody, + globalPickVersion, + calculatedRuleDiff, + transaction, + }); + + processedRules.modifiedPrebuiltRuleAssets.push(modifiedPrebuiltRuleAsset); + + return processedRules; + } catch (err) { + processedRules.processingErrors.push({ + error: err, + item: { rule_id: ruleId }, + }); + + return processedRules; } + }, + { + modifiedPrebuiltRuleAssets: [], + processingErrors: [], } + ); - const modifiedPrebuiltRuleAsset = createModifiedPrebuiltRuleAsset({ - upgradeableRule, - fieldNames, - requestBody, - globalPickVersion, - calculatedRuleDiff, - }); - - processedRules.modifiedPrebuiltRuleAssets.push(modifiedPrebuiltRuleAsset); - - return processedRules; - } catch (err) { - processedRules.processingErrors.push({ - error: err, - item: { rule_id: ruleId }, - }); - - return processedRules; - } - }, - { - modifiedPrebuiltRuleAssets: [], - processingErrors: [], - } - ); - - return { - modifiedPrebuiltRuleAssets, - processingErrors, - }; + return { + modifiedPrebuiltRuleAssets, + processingErrors, + }; + }); }; interface CreateModifiedPrebuiltRuleAssetParams { @@ -115,6 +125,7 @@ interface CreateModifiedPrebuiltRuleAssetParams { globalPickVersion: PickVersionValues; requestBody: PerformRuleUpgradeRequestBody; calculatedRuleDiff: AllFieldsDiff; + transaction: Transaction; } function createModifiedPrebuiltRuleAsset({ @@ -123,20 +134,23 @@ function createModifiedPrebuiltRuleAsset({ globalPickVersion, requestBody, calculatedRuleDiff, + transaction, }: CreateModifiedPrebuiltRuleAssetParams): PrebuiltRuleAsset { - const modifiedPrebuiltRuleAsset = {} as Record; - - for (const fieldName of fieldNames) { - modifiedPrebuiltRuleAsset[fieldName] = getValueForField({ - fieldName, - upgradeableRule, - globalPickVersion, - requestBody, - ruleFieldsDiff: calculatedRuleDiff, - }); - } - - return modifiedPrebuiltRuleAsset as PrebuiltRuleAsset; + return withSyncSecuritySpan(transaction, 'createModifiedPrebuiltRuleAsset', () => { + const modifiedPrebuiltRuleAsset = {} as Record; + + for (const fieldName of fieldNames) { + modifiedPrebuiltRuleAsset[fieldName] = getValueForField({ + fieldName, + upgradeableRule, + globalPickVersion, + requestBody, + ruleFieldsDiff: calculatedRuleDiff, + }); + } + + return modifiedPrebuiltRuleAsset as PrebuiltRuleAsset; + }); } const getFieldsDiffConflicts = (ruleFieldsDiff: Partial) => diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts index acfdb674c309a..87900c2e63b90 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { type Transaction } from 'elastic-apm-node'; +import { withSyncSecuritySpan } from '../../../../../utils/with_security_span'; import type { RuleResponse, RuleUpgradeSpecifier, @@ -20,64 +21,68 @@ export const getUpgradeableRules = ({ currentRules, versionSpecifiers, mode, + transaction, }: { rawUpgradeableRules: RuleTriad[]; currentRules: RuleResponse[]; versionSpecifiers?: RuleUpgradeSpecifier[]; mode: Mode; + transaction: Transaction; }) => { - const upgradeableRules = new Map( - rawUpgradeableRules.map((_rule) => [_rule.current.rule_id, _rule]) - ); - const fetchErrors: Array> = []; - const skippedRules: SkippedRuleUpgrade[] = []; + return withSyncSecuritySpan(transaction, 'getUpgradeableRules', () => { + const upgradeableRules = new Map( + rawUpgradeableRules.map((_rule) => [_rule.current.rule_id, _rule]) + ); + const fetchErrors: Array> = []; + const skippedRules: SkippedRuleUpgrade[] = []; - if (mode === ModeEnum.SPECIFIC_RULES) { - const installedRuleIds = new Set(currentRules.map((rule) => rule.rule_id)); - const upgradeableRuleIds = new Set(rawUpgradeableRules.map(({ current }) => current.rule_id)); - versionSpecifiers?.forEach((rule) => { - // Check that the requested rule was found - if (!installedRuleIds.has(rule.rule_id)) { - fetchErrors.push({ - error: new Error( - `Rule with rule_id "${rule.rule_id}" and version "${rule.version}" not found` - ), - item: rule, - }); - return; - } + if (mode === ModeEnum.SPECIFIC_RULES) { + const installedRuleIds = new Set(currentRules.map((rule) => rule.rule_id)); + const upgradeableRuleIds = new Set(rawUpgradeableRules.map(({ current }) => current.rule_id)); + versionSpecifiers?.forEach((rule) => { + // Check that the requested rule was found + if (!installedRuleIds.has(rule.rule_id)) { + fetchErrors.push({ + error: new Error( + `Rule with rule_id "${rule.rule_id}" and version "${rule.version}" not found` + ), + item: rule, + }); + return; + } - // Check that the requested rule is upgradeable - if (!upgradeableRuleIds.has(rule.rule_id)) { - skippedRules.push({ - rule_id: rule.rule_id, - reason: SkipRuleUpgradeReasonEnum.RULE_UP_TO_DATE, - }); - return; - } + // Check that the requested rule is upgradeable + if (!upgradeableRuleIds.has(rule.rule_id)) { + skippedRules.push({ + rule_id: rule.rule_id, + reason: SkipRuleUpgradeReasonEnum.RULE_UP_TO_DATE, + }); + return; + } - // Check that rule revisions match (no update slipped in since the user reviewed the list) - const currentRevision = currentRules.find( - (currentRule) => currentRule.rule_id === rule.rule_id - )?.revision; - if (rule.revision !== currentRevision) { - fetchErrors.push({ - error: new Error( - `Revision mismatch for rule_id ${rule.rule_id}: expected ${currentRevision}, got ${rule.revision}` - ), - item: rule, - }); - // Remove the rule from the list of upgradeable rules - if (upgradeableRules.has(rule.rule_id)) { - upgradeableRules.delete(rule.rule_id); + // Check that rule revisions match (no update slipped in since the user reviewed the list) + const currentRevision = currentRules.find( + (currentRule) => currentRule.rule_id === rule.rule_id + )?.revision; + if (rule.revision !== currentRevision) { + fetchErrors.push({ + error: new Error( + `Revision mismatch for rule_id ${rule.rule_id}: expected ${currentRevision}, got ${rule.revision}` + ), + item: rule, + }); + // Remove the rule from the list of upgradeable rules + if (upgradeableRules.has(rule.rule_id)) { + upgradeableRules.delete(rule.rule_id); + } } - } - }); - } + }); + } - return { - upgradeableRules: Array.from(upgradeableRules.values()), - fetchErrors, - skippedRules, - }; + return { + upgradeableRules: Array.from(upgradeableRules.values()), + fetchErrors, + skippedRules, + }; + }); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts index 085c41db3a5db..bc1471be9f0f4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts @@ -7,6 +7,7 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import apm from 'elastic-apm-node'; import { PERFORM_RULE_UPGRADE_URL, PerformRuleUpgradeRequestBody, @@ -48,6 +49,7 @@ export const performRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => }, }, async (context, request, response) => { + const transaction = apm.startTransaction('performRuleUpgradeRoute', 'endpoint_handler'); const siemResponse = buildSiemResponse(response); try { @@ -73,12 +75,14 @@ export const performRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => currentRules: ruleGroups.currentRules, versionSpecifiers, mode, + transaction, }); const { modifiedPrebuiltRuleAssets, processingErrors } = createModifiedPrebuiltRuleAssets( { upgradeableRules, requestBody: request.body, + transaction, } ); diff --git a/x-pack/plugins/security_solution/server/utils/with_security_span.ts b/x-pack/plugins/security_solution/server/utils/with_security_span.ts index 58787dc45d09b..5814094d51ef1 100644 --- a/x-pack/plugins/security_solution/server/utils/with_security_span.ts +++ b/x-pack/plugins/security_solution/server/utils/with_security_span.ts @@ -7,6 +7,7 @@ import type { SpanOptions } from '@kbn/apm-utils'; import { withSpan } from '@kbn/apm-utils'; import type agent from 'elastic-apm-node'; +import type { Transaction } from 'elastic-apm-node'; import { APP_ID } from '../../common/constants'; type Span = Exclude; @@ -35,3 +36,20 @@ export const withSecuritySpan = ( }, cb ); + +export const withSyncSecuritySpan = ( + parent: Transaction, + name: string, + fn: (span: Span | null) => T +): T => { + const span = parent.startSpan(name, APP_ID); + + try { + const result = fn(span); + return result; + } finally { + if (span) { + span.end(); + } + } +}; From 6f10238e84fe2cb39a6132d937509614dbfc06be Mon Sep 17 00:00:00 2001 From: jpdjere Date: Fri, 25 Oct 2024 16:35:19 -0300 Subject: [PATCH 2/2] Changes --- .../create_upgradeable_rules_payload.ts | 36 ++++++++----------- .../get_upgradeable_rules.ts | 5 +-- .../perform_rule_upgrade_route.ts | 4 --- .../server/utils/with_security_span.ts | 11 ++---- 4 files changed, 18 insertions(+), 38 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts index 956c6a1e6e2bd..1490faa2094d0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { Transaction } from 'elastic-apm-node'; import { pickBy } from 'lodash'; import { withSyncSecuritySpan } from '../../../../../utils/with_security_span'; import type { PromisePoolError } from '../../../../../utils/promise_pool'; @@ -27,7 +26,6 @@ import { getValueForField } from './get_value_for_field'; interface CreateModifiedPrebuiltRuleAssetsProps { upgradeableRules: RuleTriad[]; requestBody: PerformRuleUpgradeRequestBody; - transaction: Transaction; } interface ProcessedRules { @@ -38,9 +36,8 @@ interface ProcessedRules { export const createModifiedPrebuiltRuleAssets = ({ upgradeableRules, requestBody, - transaction, }: CreateModifiedPrebuiltRuleAssetsProps) => { - return withSyncSecuritySpan(transaction, 'createModifiedPrebuiltRuleAssets', () => { + return withSyncSecuritySpan('createModifiedPrebuiltRuleAssets', () => { const { pick_version: globalPickVersion = PickVersionValuesEnum.MERGED, mode } = requestBody; const { modifiedPrebuiltRuleAssets, processingErrors } = @@ -91,7 +88,6 @@ export const createModifiedPrebuiltRuleAssets = ({ requestBody, globalPickVersion, calculatedRuleDiff, - transaction, }); processedRules.modifiedPrebuiltRuleAssets.push(modifiedPrebuiltRuleAsset); @@ -125,7 +121,6 @@ interface CreateModifiedPrebuiltRuleAssetParams { globalPickVersion: PickVersionValues; requestBody: PerformRuleUpgradeRequestBody; calculatedRuleDiff: AllFieldsDiff; - transaction: Transaction; } function createModifiedPrebuiltRuleAsset({ @@ -134,23 +129,20 @@ function createModifiedPrebuiltRuleAsset({ globalPickVersion, requestBody, calculatedRuleDiff, - transaction, }: CreateModifiedPrebuiltRuleAssetParams): PrebuiltRuleAsset { - return withSyncSecuritySpan(transaction, 'createModifiedPrebuiltRuleAsset', () => { - const modifiedPrebuiltRuleAsset = {} as Record; - - for (const fieldName of fieldNames) { - modifiedPrebuiltRuleAsset[fieldName] = getValueForField({ - fieldName, - upgradeableRule, - globalPickVersion, - requestBody, - ruleFieldsDiff: calculatedRuleDiff, - }); - } - - return modifiedPrebuiltRuleAsset as PrebuiltRuleAsset; - }); + const modifiedPrebuiltRuleAsset = {} as Record; + + for (const fieldName of fieldNames) { + modifiedPrebuiltRuleAsset[fieldName] = getValueForField({ + fieldName, + upgradeableRule, + globalPickVersion, + requestBody, + ruleFieldsDiff: calculatedRuleDiff, + }); + } + + return modifiedPrebuiltRuleAsset as PrebuiltRuleAsset; } const getFieldsDiffConflicts = (ruleFieldsDiff: Partial) => diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts index 87900c2e63b90..5dd76b5347efe 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { type Transaction } from 'elastic-apm-node'; import { withSyncSecuritySpan } from '../../../../../utils/with_security_span'; import type { RuleResponse, @@ -21,15 +20,13 @@ export const getUpgradeableRules = ({ currentRules, versionSpecifiers, mode, - transaction, }: { rawUpgradeableRules: RuleTriad[]; currentRules: RuleResponse[]; versionSpecifiers?: RuleUpgradeSpecifier[]; mode: Mode; - transaction: Transaction; }) => { - return withSyncSecuritySpan(transaction, 'getUpgradeableRules', () => { + return withSyncSecuritySpan('getUpgradeableRules', () => { const upgradeableRules = new Map( rawUpgradeableRules.map((_rule) => [_rule.current.rule_id, _rule]) ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts index bc1471be9f0f4..085c41db3a5db 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts @@ -7,7 +7,6 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; -import apm from 'elastic-apm-node'; import { PERFORM_RULE_UPGRADE_URL, PerformRuleUpgradeRequestBody, @@ -49,7 +48,6 @@ export const performRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => }, }, async (context, request, response) => { - const transaction = apm.startTransaction('performRuleUpgradeRoute', 'endpoint_handler'); const siemResponse = buildSiemResponse(response); try { @@ -75,14 +73,12 @@ export const performRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => currentRules: ruleGroups.currentRules, versionSpecifiers, mode, - transaction, }); const { modifiedPrebuiltRuleAssets, processingErrors } = createModifiedPrebuiltRuleAssets( { upgradeableRules, requestBody: request.body, - transaction, } ); diff --git a/x-pack/plugins/security_solution/server/utils/with_security_span.ts b/x-pack/plugins/security_solution/server/utils/with_security_span.ts index 5814094d51ef1..fc6f1aafda01a 100644 --- a/x-pack/plugins/security_solution/server/utils/with_security_span.ts +++ b/x-pack/plugins/security_solution/server/utils/with_security_span.ts @@ -6,8 +6,7 @@ */ import type { SpanOptions } from '@kbn/apm-utils'; import { withSpan } from '@kbn/apm-utils'; -import type agent from 'elastic-apm-node'; -import type { Transaction } from 'elastic-apm-node'; +import agent from 'elastic-apm-node'; import { APP_ID } from '../../common/constants'; type Span = Exclude; @@ -37,12 +36,8 @@ export const withSecuritySpan = ( cb ); -export const withSyncSecuritySpan = ( - parent: Transaction, - name: string, - fn: (span: Span | null) => T -): T => { - const span = parent.startSpan(name, APP_ID); +export const withSyncSecuritySpan = (name: string, fn: (span: Span | null) => T): T => { + const span = agent.startSpan(name, APP_ID); try { const result = fn(span);