@adobe-commerce/aio-toolkit 1.0.13 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -558,7 +558,10 @@ var validator_default = Validator;
558
558
 
559
559
  // src/framework/telemetry/index.ts
560
560
  import { Core } from "@adobe/aio-sdk";
561
- import { getLogger } from "@adobe/aio-lib-telemetry";
561
+ import {
562
+ instrument as aioInstrument,
563
+ getInstrumentationHelpers
564
+ } from "@adobe/aio-lib-telemetry";
562
565
 
563
566
  // src/framework/telemetry/new-relic/index.ts
564
567
  import {
@@ -1254,7 +1257,96 @@ var new_relic_default = NewRelicTelemetry;
1254
1257
  // src/framework/telemetry/index.ts
1255
1258
  var _Telemetry = class _Telemetry {
1256
1259
  /**
1257
- * Create a logger with standard configuration and automatic metadata injection.
1260
+ * Create a new Telemetry instance
1261
+ *
1262
+ * Initializes the telemetry manager with optional runtime parameters.
1263
+ * Parameters can be set later using setParams() if not provided during construction.
1264
+ *
1265
+ * @param params - Optional runtime parameters containing telemetry configuration
1266
+ *
1267
+ * @example
1268
+ * ```typescript
1269
+ * const telemetry = new Telemetry({
1270
+ * ENABLE_TELEMETRY: true,
1271
+ * LOG_LEVEL: 'debug',
1272
+ * action_type: 'webhook'
1273
+ * });
1274
+ * ```
1275
+ */
1276
+ constructor(params = void 0) {
1277
+ /**
1278
+ * Runtime parameters containing telemetry configuration
1279
+ *
1280
+ * Stores the action parameters including telemetry flags (ENABLE_TELEMETRY),
1281
+ * provider configuration (NEW_RELIC_*), log levels, and request context.
1282
+ *
1283
+ * @private
1284
+ */
1285
+ this.params = void 0;
1286
+ /**
1287
+ * Logger instance
1288
+ *
1289
+ * Stores the logger instance for the telemetry instance.
1290
+ *
1291
+ * @private
1292
+ */
1293
+ this.logger = void 0;
1294
+ this.params = params;
1295
+ }
1296
+ /**
1297
+ * Get the current runtime parameters
1298
+ *
1299
+ * Returns the parameters stored in this telemetry instance, or an empty object
1300
+ * if no parameters have been set. These parameters control telemetry behavior
1301
+ * and provide context for logging and tracing.
1302
+ *
1303
+ * @returns Runtime parameters object (never undefined, returns empty object if not set)
1304
+ *
1305
+ * @example
1306
+ * ```typescript
1307
+ * const currentParams = telemetry.getParams();
1308
+ * if (currentParams.ENABLE_TELEMETRY) {
1309
+ * // telemetry is enabled
1310
+ * }
1311
+ * ```
1312
+ */
1313
+ getParams() {
1314
+ return this.params || {};
1315
+ }
1316
+ /**
1317
+ * Set or update the runtime parameters
1318
+ *
1319
+ * Updates the parameters used by this telemetry instance. This is typically
1320
+ * called by the initialize() method when the action receives runtime parameters,
1321
+ * but can be used to update configuration at any time.
1322
+ *
1323
+ * @param params - New runtime parameters to store
1324
+ *
1325
+ * @example
1326
+ * ```typescript
1327
+ * telemetry.setParams({
1328
+ * ENABLE_TELEMETRY: true,
1329
+ * NEW_RELIC_TELEMETRY: true,
1330
+ * LOG_LEVEL: 'info'
1331
+ * });
1332
+ * const logger = telemetry.createLogger('my-action');
1333
+ * ```
1334
+ */
1335
+ setParams(params) {
1336
+ this.params = params;
1337
+ }
1338
+ /**
1339
+ * Get the logger instance
1340
+ *
1341
+ * Returns the logger instance for the telemetry instance.
1342
+ *
1343
+ * @returns Logger instance
1344
+ */
1345
+ getLogger() {
1346
+ return this.logger;
1347
+ }
1348
+ /**
1349
+ * Create a logger with standard configuration and automatic metadata injection
1258
1350
  *
1259
1351
  * This method creates a structured logger and wraps it to automatically add
1260
1352
  * contextual metadata to all log calls:
@@ -1266,50 +1358,30 @@ var _Telemetry = class _Telemetry {
1266
1358
  * automatically appear in all logs sent to New Relic if ENABLE_TELEMETRY is true.
1267
1359
  *
1268
1360
  * @param name - Logger name (typically action name)
1269
- * @param params - Runtime parameters containing LOG_LEVEL, optional ENABLE_TELEMETRY, ENVIRONMENT, action_type, and __ow_headers
1270
1361
  * @returns Configured logger instance with automatic metadata injection
1271
1362
  *
1272
- * @example Basic string message
1273
- * ```typescript
1274
- * const logger = Telemetry.createLogger("my-action", params);
1275
- * logger.info("Processing started");
1276
- * // Logs: "Processing started"
1277
- * ```
1278
- *
1279
- * @example JSON object message with automatic metadata
1363
+ * @example
1280
1364
  * ```typescript
1281
- * const logger = Telemetry.createLogger("my-action", {
1282
- * ...params,
1283
- * action_type: "webhook",
1284
- * __ow_headers: { 'x-adobe-commerce-request-id': 'req-123' }
1285
- * });
1286
- * logger.info({
1287
- * message: "User action completed",
1288
- * user_id: "123"
1289
- * });
1290
- * // In New Relic: {
1291
- * // message: "User action completed",
1292
- * // user_id: "123",
1293
- * // "x-adobe-commerce-request-id": "req-123",
1294
- * // "action.type": "webhook",
1295
- * // environment: "development",
1296
- * // ...
1297
- * // }
1365
+ * const logger = telemetry.createLogger('my-action');
1366
+ * logger.info({ message: 'User action completed', user_id: '123' });
1367
+ * // In New Relic: includes message, user_id, request-id, action.type, environment
1298
1368
  * ```
1299
1369
  */
1300
- static createLogger(name, params) {
1301
- const baseLogger = !params.ENABLE_TELEMETRY ? Core.Logger(name, {
1302
- level: params.LOG_LEVEL || "info"
1303
- }) : getLogger(name, {
1304
- level: params.LOG_LEVEL || "info"
1370
+ createLogger(name) {
1371
+ let baseLogger = Core.Logger(name, {
1372
+ level: this.params?.LOG_LEVEL || "info"
1305
1373
  });
1374
+ if (this.params?.ENABLE_TELEMETRY === true) {
1375
+ const helpers = getInstrumentationHelpers();
1376
+ baseLogger = helpers.logger;
1377
+ }
1306
1378
  const metadata = {};
1307
- const headers = params.__ow_headers;
1379
+ const headers = this.params?.__ow_headers;
1308
1380
  const requestId = headers?.["x-adobe-commerce-request-id"];
1309
1381
  if (requestId && requestId !== "") {
1310
1382
  metadata["x-adobe-commerce-request-id"] = requestId;
1311
1383
  }
1312
- const actionType = params.action_type;
1384
+ const actionType = this.params?.action_type;
1313
1385
  if (actionType && actionType !== "") {
1314
1386
  metadata["action.type"] = actionType;
1315
1387
  }
@@ -1346,10 +1418,11 @@ var _Telemetry = class _Telemetry {
1346
1418
  }
1347
1419
  }, "error")
1348
1420
  };
1349
- return {
1421
+ this.logger = {
1350
1422
  ...baseLogger,
1351
1423
  ...wrapper
1352
1424
  };
1425
+ return this.logger;
1353
1426
  }
1354
1427
  /**
1355
1428
  * Extract structured error information for logging
@@ -1363,13 +1436,13 @@ var _Telemetry = class _Telemetry {
1363
1436
  * @example
1364
1437
  * ```typescript
1365
1438
  * try {
1366
- * // some operation
1439
+ * await processOrder(orderId);
1367
1440
  * } catch (error) {
1368
- * logger.error("Operation failed", Telemetry.formatError(error));
1441
+ * logger.error({ message: 'Operation failed', ...telemetry.formatError(error) });
1369
1442
  * }
1370
1443
  * ```
1371
1444
  */
1372
- static formatError(error) {
1445
+ formatError(error) {
1373
1446
  if (error instanceof Error) {
1374
1447
  return {
1375
1448
  error_name: error.name,
@@ -1379,6 +1452,77 @@ var _Telemetry = class _Telemetry {
1379
1452
  }
1380
1453
  return { error: String(error) };
1381
1454
  }
1455
+ /**
1456
+ * Get the current OpenTelemetry span for adding attributes and events
1457
+ *
1458
+ * This method provides access to the currently active span, allowing you to
1459
+ * add custom attributes and events for enhanced observability in New Relic.
1460
+ *
1461
+ * **IMPORTANT**: This must be called within an active trace context
1462
+ * (i.e., inside a function that has been wrapped by Telemetry.initialize()).
1463
+ * If called outside an active context or if ENABLE_TELEMETRY is false, it returns null.
1464
+ *
1465
+ * @returns The current span object, or null if no active span exists
1466
+ *
1467
+ * @example
1468
+ * ```typescript
1469
+ * const processOrder = async (orderId: string) => {
1470
+ * const span = telemetry.getCurrentSpan();
1471
+ * if (span) {
1472
+ * span.setAttribute('order.id', orderId);
1473
+ * span.setAttribute('order.status', 'processing');
1474
+ * span.addEvent('processing-started');
1475
+ * }
1476
+ * // ... process order logic ...
1477
+ * };
1478
+ * ```
1479
+ */
1480
+ getCurrentSpan() {
1481
+ if (this.params?.ENABLE_TELEMETRY === true) {
1482
+ const helpers = getInstrumentationHelpers();
1483
+ return helpers?.currentSpan || null;
1484
+ }
1485
+ return null;
1486
+ }
1487
+ /**
1488
+ * Instrument a function with OpenTelemetry for distributed tracing
1489
+ *
1490
+ * This method wraps any function with OpenTelemetry instrumentation,
1491
+ * creating a child span in the current trace context. Use this to create
1492
+ * detailed spans for specific operations within your action.
1493
+ *
1494
+ * **IMPORTANT**: The instrumented function must be called within an active trace context
1495
+ * (i.e., inside a function that has been wrapped by Telemetry.initialize()).
1496
+ * If called outside an active context, the span will not be created or exported.
1497
+ *
1498
+ * @param spanName - Name for the span (appears in New Relic as the span name)
1499
+ * @param fn - The function to instrument (sync or async)
1500
+ * @returns The instrumented function that creates a child span when called
1501
+ *
1502
+ * @example
1503
+ * ```typescript
1504
+ * const fetchUserData = telemetry.instrument(
1505
+ * 'fetch-user-data',
1506
+ * async (userId: string) => {
1507
+ * const span = telemetry.getCurrentSpan();
1508
+ * if (span) {
1509
+ * span.setAttribute('user.id', userId);
1510
+ * span.addEvent('fetching-started');
1511
+ * }
1512
+ * const response = await fetch(`/api/users/${userId}`);
1513
+ * return response.json();
1514
+ * }
1515
+ * );
1516
+ * const userData = await fetchUserData('123');
1517
+ * ```
1518
+ */
1519
+ instrument(spanName, fn) {
1520
+ return aioInstrument(fn, {
1521
+ spanConfig: {
1522
+ spanName
1523
+ }
1524
+ });
1525
+ }
1382
1526
  /**
1383
1527
  * Initialize telemetry for a runtime action with provider fallback chain
1384
1528
  *
@@ -1396,60 +1540,20 @@ var _Telemetry = class _Telemetry {
1396
1540
  * is returned to alert you of the misconfiguration. This prevents silently running
1397
1541
  * without telemetry when you expect it to be working.
1398
1542
  *
1399
- * Environment Configuration:
1400
- * Pass params.ENVIRONMENT to set the environment field in all logs.
1401
- * This can be set via environment variables in your action configuration.
1402
- *
1403
1543
  * @param action - The runtime action function to instrument
1404
1544
  * @returns The instrumented action ready for export
1405
1545
  *
1406
- * @example Single provider (New Relic) - Valid configuration
1407
- * ```typescript
1408
- * // Environment:
1409
- * // ENABLE_TELEMETRY=true
1410
- * // NEW_RELIC_TELEMETRY=true
1411
- * // NEW_RELIC_SERVICE_NAME=my-service
1412
- * // NEW_RELIC_LICENSE_KEY=xxxxx
1413
- *
1414
- * export const main = Telemetry.initialize(myAction);
1415
- * // ✅ Uses New Relic telemetry
1416
- * ```
1417
- *
1418
- * @example New Relic enabled but misconfigured - Returns error response
1419
- * ```typescript
1420
- * // Environment:
1421
- * // ENABLE_TELEMETRY=true
1422
- * // NEW_RELIC_TELEMETRY=true
1423
- * // NEW_RELIC_SERVICE_NAME=my-service
1424
- * // Missing NEW_RELIC_LICENSE_KEY!
1425
- *
1426
- * export const main = Telemetry.initialize(myAction);
1427
- * // ❌ Returns { error: { statusCode: 500, body: { error: "Telemetry configuration error: NEW_RELIC_LICENSE_KEY is required" } } }
1428
- * // This is intentional - you want to know your telemetry config is broken!
1429
- * ```
1430
- *
1431
- * @example Multi-provider fallback
1432
- * ```typescript
1433
- * // Environment:
1434
- * // ENABLE_TELEMETRY=true
1435
- * // NEW_RELIC_TELEMETRY=false // Skip New Relic
1436
- * // GRAFANA_TELEMETRY=true // Use Grafana instead
1437
- *
1438
- * export const main = Telemetry.initialize(myAction);
1439
- * // ✅ Skips New Relic, tries Grafana (when implemented)
1440
- * ```
1441
- *
1442
- * @example No telemetry
1546
+ * @example
1443
1547
  * ```typescript
1444
- * // Environment:
1445
- * // ENABLE_TELEMETRY=false (or not set)
1446
- *
1447
- * export const main = Telemetry.initialize(myAction);
1448
- * // ✅ Returns original action without instrumentation
1548
+ * const telemetry = new Telemetry();
1549
+ * export const main = telemetry.initialize(myAction);
1550
+ * // Environment: ENABLE_TELEMETRY=true, NEW_RELIC_TELEMETRY=true
1551
+ * // Result: Uses New Relic telemetry with full distributed tracing
1449
1552
  * ```
1450
1553
  */
1451
- static initialize(action) {
1554
+ initialize(action) {
1452
1555
  return async (params) => {
1556
+ this.setParams(params);
1453
1557
  const newRelicTelemetry = new new_relic_default();
1454
1558
  if (newRelicTelemetry.canInitialize(params)) {
1455
1559
  try {
@@ -1524,7 +1628,7 @@ var _RuntimeAction = class _RuntimeAction {
1524
1628
  * @param requiredHeaders - Required header names (case-insensitive)
1525
1629
  * @param action - User-defined action function that receives:
1526
1630
  * - params: All request parameters including __ow_* runtime parameters
1527
- * - ctx: Context object with logger and headers
1631
+ * - ctx: Context object with logger, headers, and telemetry
1528
1632
  * @returns Wrapped action function ready to be exported as Adobe I/O Runtime entrypoint
1529
1633
  *
1530
1634
  * @example With All Options
@@ -1534,10 +1638,16 @@ var _RuntimeAction = class _RuntimeAction {
1534
1638
  * [HttpMethod.POST],
1535
1639
  * ['orderId', 'customerId'],
1536
1640
  * ['authorization', 'x-api-key'],
1537
- * async (params, { logger, headers }) => {
1641
+ * async (params, { logger, headers, telemetry }) => {
1538
1642
  * const { orderId, customerId } = params;
1539
1643
  * logger.info({ message: 'Processing order', orderId, customerId });
1540
1644
  *
1645
+ * // Access current span for custom instrumentation
1646
+ * const span = telemetry.getCurrentSpan();
1647
+ * if (span) {
1648
+ * span.setAttribute('order.id', orderId);
1649
+ * }
1650
+ *
1541
1651
  * // Your business logic here
1542
1652
  * const result = await processOrderLogic(orderId, customerId);
1543
1653
  *
@@ -1560,11 +1670,13 @@ var _RuntimeAction = class _RuntimeAction {
1560
1670
  static execute(name = "main", httpMethods = [], requiredParams = [], requiredHeaders = [], action = async (_params) => {
1561
1671
  return { statusCode: 200 /* OK */, body: {} };
1562
1672
  }) {
1673
+ const telemetry = new telemetry_default();
1563
1674
  const runtimeAction = /* @__PURE__ */ __name(async (params) => {
1564
1675
  if (!params.action_type) {
1565
1676
  params.action_type = _RuntimeAction.getActionType();
1566
1677
  }
1567
- const logger = telemetry_default.createLogger(name, params);
1678
+ telemetry.setParams(params);
1679
+ const logger = telemetry.createLogger(name);
1568
1680
  try {
1569
1681
  logger.debug({
1570
1682
  message: `${_RuntimeAction.getActionTypeName()} execution started`
@@ -1581,18 +1693,26 @@ var _RuntimeAction = class _RuntimeAction {
1581
1693
  body: params.__ow_body || {}
1582
1694
  })
1583
1695
  );
1584
- const validationError = _RuntimeAction.validateRequest(
1696
+ const validationError = _RuntimeAction.validateRequestWithInstrumentation(
1697
+ name,
1585
1698
  params,
1586
1699
  requiredParams,
1587
1700
  requiredHeaders,
1588
1701
  httpMethods,
1589
- logger,
1590
- name
1702
+ telemetry,
1703
+ logger
1591
1704
  );
1592
1705
  if (validationError) {
1593
1706
  return validationError;
1594
1707
  }
1595
- const result = await action(params, { logger, headers: params.__ow_headers || {} });
1708
+ const result = await _RuntimeAction.executeActionWithInstrumentation(
1709
+ name,
1710
+ params.__ow_headers || {},
1711
+ params,
1712
+ action,
1713
+ telemetry,
1714
+ logger
1715
+ );
1596
1716
  logger.debug(
1597
1717
  JSON.stringify({
1598
1718
  message: `${_RuntimeAction.getActionTypeName()} execution completed`,
@@ -1616,7 +1736,108 @@ var _RuntimeAction = class _RuntimeAction {
1616
1736
  return response_default.error(500 /* INTERNAL_ERROR */, "server error");
1617
1737
  }
1618
1738
  }, "runtimeAction");
1619
- return telemetry_default.initialize(runtimeAction);
1739
+ return telemetry.initialize(runtimeAction);
1740
+ }
1741
+ /**
1742
+ * Executes user's action with OpenTelemetry instrumentation
1743
+ *
1744
+ * This method wraps the action execution with distributed tracing instrumentation,
1745
+ * creating a span that tracks execution metrics and results in New Relic.
1746
+ *
1747
+ * @param name - Action name used for span naming
1748
+ * @param headers - Request headers
1749
+ * @param params - Request parameters
1750
+ * @param action - The user's action function to execute
1751
+ * @param telemetry - Telemetry instance for instrumentation
1752
+ * @param logger - Logger instance
1753
+ * @returns Promise resolving to the action result
1754
+ *
1755
+ * @private
1756
+ */
1757
+ static async executeActionWithInstrumentation(name, headers, params, action, telemetry, logger) {
1758
+ const instrumentedAction = telemetry.instrument(
1759
+ `runtime.action.${name}.execute`,
1760
+ async (actionName, actionHeaders, actionParams, actionFn, actionTelemetry, actionLogger) => {
1761
+ const span = actionTelemetry.getCurrentSpan();
1762
+ if (span) {
1763
+ span.setAttribute("action.name", actionName);
1764
+ span.setAttribute("action.type", actionParams.action_type || "unknown");
1765
+ span.setAttribute("action.has_headers", !!actionHeaders);
1766
+ span.addEvent("action-execution-started");
1767
+ }
1768
+ const result = await actionFn(actionParams, {
1769
+ logger: actionLogger,
1770
+ headers: actionHeaders,
1771
+ telemetry: actionTelemetry
1772
+ });
1773
+ if (span) {
1774
+ const statusCode = "statusCode" in result ? result.statusCode : result.error.statusCode;
1775
+ span.setAttribute("action.response_status", statusCode);
1776
+ span.setAttribute("action.has_error", "error" in result);
1777
+ span.addEvent("action-execution-completed", {
1778
+ statusCode
1779
+ });
1780
+ }
1781
+ return result;
1782
+ }
1783
+ );
1784
+ return instrumentedAction(name, headers, params, action, telemetry, logger);
1785
+ }
1786
+ /**
1787
+ * Validates incoming request with OpenTelemetry instrumentation
1788
+ *
1789
+ * This method wraps the validation logic with distributed tracing instrumentation,
1790
+ * creating a span that tracks validation metrics and results in New Relic.
1791
+ *
1792
+ * @param name - Action name used for span naming
1793
+ * @param params - Request parameters including __ow_* runtime parameters
1794
+ * @param requiredParams - List of required parameter names to validate
1795
+ * @param requiredHeaders - List of required header names to validate (case-insensitive)
1796
+ * @param httpMethods - Allowed HTTP methods. Empty array skips HTTP method validation
1797
+ * @param telemetry - Telemetry instance for instrumentation
1798
+ * @param logger - Logger instance for error logging
1799
+ * @returns RuntimeActionResponseType with error details if validation fails, null if valid
1800
+ *
1801
+ * @private
1802
+ */
1803
+ static validateRequestWithInstrumentation(name, params, requiredParams, requiredHeaders, httpMethods, telemetry, logger) {
1804
+ const instrumentedValidate = telemetry.instrument(
1805
+ `runtime.action.${name}.validate`,
1806
+ (actionName, actionParams, actionRequiredParams, actionRequiredHeaders, actionHttpMethods, actionTelemetry, actionLogger) => {
1807
+ const span = actionTelemetry.getCurrentSpan();
1808
+ if (span) {
1809
+ span.setAttribute("validation.required_params", actionRequiredParams.join(","));
1810
+ span.setAttribute("validation.required_headers", actionRequiredHeaders.join(","));
1811
+ span.setAttribute("validation.allowed_methods", actionHttpMethods.join(","));
1812
+ span.setAttribute("validation.request_method", actionParams.__ow_method || "unknown");
1813
+ }
1814
+ const validationResult = _RuntimeAction.validateRequest(
1815
+ actionName,
1816
+ actionParams,
1817
+ actionRequiredParams,
1818
+ actionRequiredHeaders,
1819
+ actionHttpMethods,
1820
+ actionLogger
1821
+ );
1822
+ if (span) {
1823
+ span.setAttribute("validation.passed", validationResult === null);
1824
+ if (validationResult) {
1825
+ const statusCode = "statusCode" in validationResult ? validationResult.statusCode : validationResult.error.statusCode;
1826
+ span.setAttribute("validation.error_code", statusCode);
1827
+ }
1828
+ }
1829
+ return validationResult;
1830
+ }
1831
+ );
1832
+ return instrumentedValidate(
1833
+ name,
1834
+ params,
1835
+ requiredParams,
1836
+ requiredHeaders,
1837
+ httpMethods,
1838
+ telemetry,
1839
+ logger
1840
+ );
1620
1841
  }
1621
1842
  /**
1622
1843
  * Validates incoming request against required parameters, headers, and HTTP methods
@@ -1626,12 +1847,12 @@ var _RuntimeAction = class _RuntimeAction {
1626
1847
  * 2. Checks for missing required headers (case-insensitive)
1627
1848
  * 3. Validates the HTTP method against allowed methods list
1628
1849
  *
1850
+ * @param _name - Action name for logging (currently unused)
1629
1851
  * @param params - Request parameters including __ow_* runtime parameters
1630
1852
  * @param requiredParams - List of required parameter names to validate
1631
1853
  * @param requiredHeaders - List of required header names to validate (case-insensitive)
1632
1854
  * @param httpMethods - Allowed HTTP methods. Empty array skips HTTP method validation
1633
1855
  * @param logger - Logger instance for error logging (child logger with request ID if present)
1634
- * @param name - Action name for logging
1635
1856
  * @returns RuntimeActionResponseType with error details if validation fails, null if valid
1636
1857
  *
1637
1858
  * @private
@@ -1651,7 +1872,7 @@ var _RuntimeAction = class _RuntimeAction {
1651
1872
  * }
1652
1873
  * ```
1653
1874
  */
1654
- static validateRequest(params, requiredParams, requiredHeaders, httpMethods, logger, _name) {
1875
+ static validateRequest(_name, params, requiredParams, requiredHeaders, httpMethods, logger) {
1655
1876
  const errorMessage = validator_default.checkMissingRequestInputs(params, requiredParams, requiredHeaders) ?? "";
1656
1877
  if (errorMessage) {
1657
1878
  logger.error({
@@ -1731,11 +1952,15 @@ var _EventConsumerAction = class _EventConsumerAction {
1731
1952
  * 'webhook-processor',
1732
1953
  * ['eventType', 'eventData'],
1733
1954
  * ['x-webhook-signature'],
1734
- * async (params, { logger, headers }) => {
1955
+ * async (params, { logger, headers, telemetry }) => {
1735
1956
  * logger.info({
1736
1957
  * message: 'Processing webhook',
1737
1958
  * eventType: params.eventType
1738
1959
  * });
1960
+ * const span = telemetry.getCurrentSpan();
1961
+ * if (span) {
1962
+ * span.setAttribute('event.type', params.eventType);
1963
+ * }
1739
1964
  * // Process event...
1740
1965
  * return { statusCode: 200, body: { processed: true } };
1741
1966
  * }
@@ -1745,9 +1970,10 @@ var _EventConsumerAction = class _EventConsumerAction {
1745
1970
  static execute(name = "main", requiredParams = [], requiredHeaders = [], action = async (_params) => {
1746
1971
  return { statusCode: 200 /* OK */, body: {} };
1747
1972
  }) {
1973
+ const telemetry = new telemetry_default();
1748
1974
  const eventConsumerAction = /* @__PURE__ */ __name(async (params) => {
1749
1975
  params.action_type = "event-consumer-action";
1750
- const logger = telemetry_default.createLogger(name, params);
1976
+ const logger = telemetry.createLogger(name);
1751
1977
  try {
1752
1978
  logger.debug({
1753
1979
  message: "Event consumer action execution started"
@@ -1760,7 +1986,13 @@ var _EventConsumerAction = class _EventConsumerAction {
1760
1986
  message: "Event consumer action parameters received",
1761
1987
  parameters: params
1762
1988
  });
1763
- const errorMessage = validator_default.checkMissingRequestInputs(params, requiredParams, requiredHeaders) || "";
1989
+ const errorMessage = _EventConsumerAction.validateWithInstrumentation(
1990
+ name,
1991
+ params,
1992
+ requiredParams,
1993
+ requiredHeaders,
1994
+ telemetry
1995
+ );
1764
1996
  if (errorMessage) {
1765
1997
  logger.error({
1766
1998
  message: "Event consumer action validation failed",
@@ -1768,7 +2000,14 @@ var _EventConsumerAction = class _EventConsumerAction {
1768
2000
  });
1769
2001
  return response_default.error(400 /* BAD_REQUEST */, errorMessage);
1770
2002
  }
1771
- const result = await action(params, { logger, headers: params.__ow_headers || {} });
2003
+ const result = await _EventConsumerAction.executeActionWithInstrumentation(
2004
+ name,
2005
+ params.__ow_headers || {},
2006
+ params,
2007
+ action,
2008
+ telemetry,
2009
+ logger
2010
+ );
1772
2011
  logger.debug({
1773
2012
  message: "Event consumer action execution completed",
1774
2013
  result
@@ -1777,16 +2016,109 @@ var _EventConsumerAction = class _EventConsumerAction {
1777
2016
  } catch (error) {
1778
2017
  if (error instanceof Error) {
1779
2018
  logger.error({
2019
+ message: "Event consumer action execution failed",
1780
2020
  error: error.message,
1781
2021
  stack: error.stack
1782
2022
  });
1783
2023
  } else {
1784
- logger.error({ error });
2024
+ logger.error({
2025
+ message: "Event consumer action execution failed",
2026
+ error
2027
+ });
1785
2028
  }
1786
2029
  return response_default.error(500 /* INTERNAL_ERROR */, "server error");
1787
2030
  }
1788
2031
  }, "eventConsumerAction");
1789
- return telemetry_default.initialize(eventConsumerAction);
2032
+ return telemetry.initialize(eventConsumerAction);
2033
+ }
2034
+ /**
2035
+ * Validates event consumer request with OpenTelemetry instrumentation
2036
+ *
2037
+ * This method wraps the validation logic with distributed tracing instrumentation,
2038
+ * creating a span that tracks validation metrics and results in New Relic.
2039
+ *
2040
+ * @param name - Action name used for span naming
2041
+ * @param params - Request parameters
2042
+ * @param requiredParams - List of required parameter names to validate
2043
+ * @param requiredHeaders - List of required header names to validate
2044
+ * @returns Error message if validation fails, empty string if successful
2045
+ *
2046
+ * @private
2047
+ */
2048
+ static validateWithInstrumentation(name, params, requiredParams, requiredHeaders, telemetry) {
2049
+ const instrumentedValidate = telemetry.instrument(
2050
+ `event.consumer.action.${name}.validate`,
2051
+ (actionName, actionParams, actionRequiredParams, actionRequiredHeaders, actionTelemetry) => {
2052
+ const span = actionTelemetry.getCurrentSpan();
2053
+ if (span) {
2054
+ span.setAttribute(
2055
+ "event.consumer.validation.required_params",
2056
+ actionRequiredParams.join(",")
2057
+ );
2058
+ span.setAttribute(
2059
+ "event.consumer.validation.required_headers",
2060
+ actionRequiredHeaders.join(",")
2061
+ );
2062
+ }
2063
+ const errorMessage = validator_default.checkMissingRequestInputs(
2064
+ actionParams,
2065
+ actionRequiredParams,
2066
+ actionRequiredHeaders
2067
+ ) || "";
2068
+ if (span) {
2069
+ span.setAttribute("event.consumer.validation.passed", errorMessage === "");
2070
+ if (errorMessage) {
2071
+ span.setAttribute("event.consumer.validation.error", errorMessage);
2072
+ }
2073
+ }
2074
+ return errorMessage;
2075
+ }
2076
+ );
2077
+ return instrumentedValidate(name, params, requiredParams, requiredHeaders, telemetry);
2078
+ }
2079
+ /**
2080
+ * Executes event consumer action with OpenTelemetry instrumentation
2081
+ *
2082
+ * This method wraps the action execution with distributed tracing instrumentation,
2083
+ * creating a span that tracks execution metrics and results in New Relic.
2084
+ *
2085
+ * @param name - Action name used for span naming
2086
+ * @param headers - Request headers
2087
+ * @param params - Request parameters
2088
+ * @param action - The user's action function to execute
2089
+ * @param telemetry - Telemetry instance for instrumentation
2090
+ * @param logger - Logger instance
2091
+ * @returns Promise resolving to the action result
2092
+ *
2093
+ * @private
2094
+ */
2095
+ static async executeActionWithInstrumentation(name, headers, params, action, telemetry, logger) {
2096
+ const instrumentedAction = telemetry.instrument(
2097
+ `event.consumer.action.${name}.execute`,
2098
+ async (actionName, actionHeaders, actionParams, actionFn, actionTelemetry, actionLogger) => {
2099
+ const span = actionTelemetry.getCurrentSpan();
2100
+ if (span) {
2101
+ span.setAttribute("event.consumer.action.name", actionName);
2102
+ span.setAttribute("event.consumer.action.has_headers", !!actionHeaders);
2103
+ span.addEvent("event-consumer-execution-started");
2104
+ }
2105
+ const result = await actionFn(actionParams, {
2106
+ logger: actionLogger,
2107
+ headers: actionHeaders,
2108
+ telemetry: actionTelemetry
2109
+ });
2110
+ if (span) {
2111
+ const statusCode = "statusCode" in result ? result.statusCode : result.error.statusCode;
2112
+ span.setAttribute("event.consumer.action.response_status", statusCode);
2113
+ span.setAttribute("event.consumer.action.has_error", "error" in result);
2114
+ span.addEvent("event-consumer-execution-completed", {
2115
+ statusCode
2116
+ });
2117
+ }
2118
+ return result;
2119
+ }
2120
+ );
2121
+ return instrumentedAction(name, headers, params, action, telemetry, logger);
1790
2122
  }
1791
2123
  };
1792
2124
  __name(_EventConsumerAction, "EventConsumerAction");
@@ -1796,6 +2128,116 @@ var event_consumer_action_default = EventConsumerAction;
1796
2128
  // src/framework/graphql-action/index.ts
1797
2129
  import { graphql, buildSchema, parse, validate } from "graphql";
1798
2130
  var _GraphQlAction = class _GraphQlAction {
2131
+ /**
2132
+ * Creates a GraphQL action handler with schema validation and telemetry
2133
+ *
2134
+ * This method wraps a GraphQL API with standardized runtime functionality:
2135
+ * 1. Builds and validates the GraphQL schema
2136
+ * 2. Parses incoming GraphQL queries
2137
+ * 3. Validates queries against the schema
2138
+ * 4. Optionally blocks introspection queries for security
2139
+ * 5. Executes queries with provided resolvers
2140
+ * 6. Integrates with OpenTelemetry for distributed tracing
2141
+ * 7. Provides structured logging with request ID correlation
2142
+ *
2143
+ * All operations are instrumented with OpenTelemetry spans for observability in New Relic.
2144
+ *
2145
+ * @param schema - GraphQL schema definition string (SDL format)
2146
+ * @param resolvers - Function that returns resolver map with access to logger, headers, and params
2147
+ * @param name - Action name for logging and telemetry spans (default: 'main')
2148
+ * @param disableIntrospection - If true, blocks introspection queries (__schema, __type) for security (default: false)
2149
+ * @returns Wrapped action function ready to be exported as Adobe I/O Runtime entrypoint
2150
+ *
2151
+ * @example Complete GraphQL API with authentication
2152
+ * ```typescript
2153
+ * const schema = `
2154
+ * type Query {
2155
+ * me: User
2156
+ * orders: [Order]
2157
+ * }
2158
+ *
2159
+ * type User {
2160
+ * id: ID!
2161
+ * email: String!
2162
+ * name: String!
2163
+ * }
2164
+ *
2165
+ * type Order {
2166
+ * id: ID!
2167
+ * total: Float!
2168
+ * items: [OrderItem]
2169
+ * }
2170
+ *
2171
+ * type OrderItem {
2172
+ * productId: ID!
2173
+ * quantity: Int!
2174
+ * price: Float!
2175
+ * }
2176
+ * `;
2177
+ *
2178
+ * const resolvers = async ({ logger, headers, params, telemetry }) => {
2179
+ * // Access authentication from headers
2180
+ * const userId = headers['x-user-id'];
2181
+ *
2182
+ * return {
2183
+ * me: async () => {
2184
+ * logger.info({ message: 'Fetching current user', userId });
2185
+ * const span = telemetry.getCurrentSpan();
2186
+ * if (span) {
2187
+ * span.setAttribute('user.id', userId);
2188
+ * }
2189
+ * return getUserById(userId);
2190
+ * },
2191
+ * orders: async () => {
2192
+ * logger.info({ message: 'Fetching user orders', userId });
2193
+ * return getOrdersByUserId(userId);
2194
+ * }
2195
+ * };
2196
+ * };
2197
+ *
2198
+ * // Disable introspection in production for security
2199
+ * const handler = GraphQlAction.execute(
2200
+ * schema,
2201
+ * resolvers,
2202
+ * 'customer-api',
2203
+ * process.env.NODE_ENV === 'production'
2204
+ * );
2205
+ *
2206
+ * export const main = handler;
2207
+ * ```
2208
+ *
2209
+ * @example GraphQL action with variables and operation names
2210
+ * ```typescript
2211
+ * // Client sends:
2212
+ * // POST /api/graphql
2213
+ * // {
2214
+ * // "query": "query GetUser($id: ID!) { user(id: $id) { name email } }",
2215
+ * // "variables": { "id": "123" },
2216
+ * // "operationName": "GetUser"
2217
+ * // }
2218
+ *
2219
+ * const schema = `
2220
+ * type Query {
2221
+ * user(id: ID!): User
2222
+ * }
2223
+ * type User {
2224
+ * id: ID!
2225
+ * name: String!
2226
+ * email: String!
2227
+ * }
2228
+ * `;
2229
+ *
2230
+ * const resolvers = async ({ logger, headers, params, telemetry }) => ({
2231
+ * user: async ({ id }) => {
2232
+ * logger.info({ message: 'Query executed', operationName: 'GetUser', userId: id });
2233
+ * return { id, name: 'John Doe', email: 'john@example.com' };
2234
+ * }
2235
+ * });
2236
+ *
2237
+ * const handler = GraphQlAction.execute(schema, resolvers, 'graphql-api');
2238
+ * export const main = handler;
2239
+ * ```
2240
+ */
1799
2241
  static execute(schema = `
1800
2242
  type Query {
1801
2243
  hello: String
@@ -1805,15 +2247,11 @@ var _GraphQlAction = class _GraphQlAction {
1805
2247
  hello: /* @__PURE__ */ __name(() => "Hello World!", "hello")
1806
2248
  };
1807
2249
  }, name = "main", disableIntrospection = false) {
1808
- runtime_action_default.setActionType("graphql-action");
1809
- runtime_action_default.setActionTypeName("GraphQL action");
1810
2250
  const callback = /* @__PURE__ */ __name(async (params, ctx) => {
1811
- const { logger } = ctx;
1812
- let graphqlSchema;
1813
- try {
1814
- graphqlSchema = buildSchema(schema);
1815
- } catch (error) {
1816
- return response_default.error(400 /* BAD_REQUEST */, error.message);
2251
+ const { logger, telemetry } = ctx;
2252
+ const { schema: graphqlSchema, error: schemaError } = _GraphQlAction.buildSchemaWithInstrumentation(name, schema, telemetry);
2253
+ if (schemaError) {
2254
+ return response_default.error(400 /* BAD_REQUEST */, schemaError);
1817
2255
  }
1818
2256
  const graphqlResolvers = await resolvers({
1819
2257
  ...ctx,
@@ -1823,11 +2261,9 @@ var _GraphQlAction = class _GraphQlAction {
1823
2261
  });
1824
2262
  const context2 = {};
1825
2263
  const query = params.query;
1826
- let parsedQuery;
1827
- try {
1828
- parsedQuery = parse(query);
1829
- } catch (error) {
1830
- return response_default.error(400 /* BAD_REQUEST */, error.message);
2264
+ const { parsed: parsedQuery, error: parseError } = _GraphQlAction.parseQueryWithInstrumentation(name, query, telemetry);
2265
+ if (parseError) {
2266
+ return response_default.error(400 /* BAD_REQUEST */, parseError);
1831
2267
  }
1832
2268
  logger.debug(
1833
2269
  JSON.stringify({
@@ -1835,7 +2271,12 @@ var _GraphQlAction = class _GraphQlAction {
1835
2271
  query: parsedQuery
1836
2272
  })
1837
2273
  );
1838
- const validationErrors = validate(graphqlSchema, parsedQuery);
2274
+ const validationErrors = _GraphQlAction.validateQueryWithInstrumentation(
2275
+ name,
2276
+ graphqlSchema,
2277
+ parsedQuery,
2278
+ telemetry
2279
+ );
1839
2280
  if (validationErrors.length) {
1840
2281
  logger.error({
1841
2282
  message: "GraphQL action query validation failed",
@@ -1850,10 +2291,10 @@ var _GraphQlAction = class _GraphQlAction {
1850
2291
  logger.debug({
1851
2292
  message: "GraphQL action introspection check disabled"
1852
2293
  });
1853
- const isIntrospectionQuery = parsedQuery.definitions.some(
1854
- (definition) => definition.selectionSet.selections.some(
1855
- (selection) => selection.name.value.startsWith("__")
1856
- )
2294
+ const isIntrospectionQuery = _GraphQlAction.checkIntrospectionWithInstrumentation(
2295
+ name,
2296
+ parsedQuery,
2297
+ telemetry
1857
2298
  );
1858
2299
  if (isIntrospectionQuery) {
1859
2300
  logger.error({
@@ -1872,24 +2313,240 @@ var _GraphQlAction = class _GraphQlAction {
1872
2313
  variables
1873
2314
  });
1874
2315
  return response_default.success(
1875
- await graphql({
1876
- schema: graphqlSchema,
1877
- source: query,
1878
- rootValue: graphqlResolvers,
1879
- contextValue: context2,
1880
- variableValues: variables,
1881
- operationName: params.operationName
1882
- })
2316
+ await _GraphQlAction.executeGraphQLWithInstrumentation(
2317
+ name,
2318
+ {
2319
+ schema: graphqlSchema,
2320
+ source: query,
2321
+ rootValue: graphqlResolvers,
2322
+ contextValue: context2,
2323
+ variableValues: variables,
2324
+ operationName: params.operationName
2325
+ },
2326
+ telemetry
2327
+ )
1883
2328
  );
1884
2329
  }, "callback");
1885
- const graphqlAction = runtime_action_default.execute(
2330
+ runtime_action_default.setActionType("graphql-action");
2331
+ runtime_action_default.setActionTypeName("GraphQL action");
2332
+ return runtime_action_default.execute(
1886
2333
  `graphql-${name}`,
1887
2334
  ["GET" /* GET */, "POST" /* POST */],
1888
2335
  ["query"],
1889
2336
  [],
1890
2337
  callback
1891
2338
  );
1892
- return telemetry_default.initialize(graphqlAction);
2339
+ }
2340
+ /**
2341
+ * Builds GraphQL schema with OpenTelemetry instrumentation
2342
+ *
2343
+ * This method wraps the schema building logic with distributed tracing instrumentation,
2344
+ * creating a span that tracks schema metrics and results in New Relic.
2345
+ *
2346
+ * @param name - Action name used for span naming
2347
+ * @param schemaString - GraphQL schema definition string
2348
+ * @param params - Request parameters for telemetry context
2349
+ * @returns Object with built schema or error message
2350
+ *
2351
+ * @private
2352
+ */
2353
+ static buildSchemaWithInstrumentation(name, schemaString, telemetry) {
2354
+ const instrumentedBuildSchema = telemetry.instrument(
2355
+ `graphql.action.${name}.build-schema`,
2356
+ (actionName, actionSchemaString, actionTelemetry) => {
2357
+ const span = actionTelemetry.getCurrentSpan();
2358
+ if (span) {
2359
+ span.setAttribute("graphql.schema.length", actionSchemaString.length);
2360
+ span.addEvent("schema-building-started");
2361
+ }
2362
+ try {
2363
+ const builtSchema = buildSchema(actionSchemaString);
2364
+ if (span) {
2365
+ span.setAttribute("graphql.schema.built", true);
2366
+ span.addEvent("schema-building-completed");
2367
+ }
2368
+ return { schema: builtSchema, error: null };
2369
+ } catch (error) {
2370
+ if (span) {
2371
+ span.setAttribute("graphql.schema.built", false);
2372
+ span.setAttribute("graphql.schema.error", error.message);
2373
+ }
2374
+ return { schema: null, error: error.message };
2375
+ }
2376
+ }
2377
+ );
2378
+ return instrumentedBuildSchema(name, schemaString, telemetry);
2379
+ }
2380
+ /**
2381
+ * Parses GraphQL query with OpenTelemetry instrumentation
2382
+ *
2383
+ * This method wraps the query parsing logic with distributed tracing instrumentation,
2384
+ * creating a span that tracks parsing metrics and results in New Relic.
2385
+ *
2386
+ * @param name - Action name used for span naming
2387
+ * @param queryString - GraphQL query string to parse
2388
+ * @param params - Request parameters for telemetry context
2389
+ * @returns Object with parsed query document or error message
2390
+ *
2391
+ * @private
2392
+ */
2393
+ static parseQueryWithInstrumentation(name, queryString, telemetry) {
2394
+ const instrumentedParseQuery = telemetry.instrument(
2395
+ `graphql.action.${name}.parse-query`,
2396
+ (actionName, actionQueryString, actionTelemetry) => {
2397
+ const span = actionTelemetry.getCurrentSpan();
2398
+ if (span) {
2399
+ span.setAttribute("graphql.query.length", actionQueryString.length);
2400
+ span.addEvent("query-parsing-started");
2401
+ }
2402
+ try {
2403
+ const parsed = parse(actionQueryString);
2404
+ if (span) {
2405
+ span.setAttribute("graphql.query.parsed", true);
2406
+ span.setAttribute("graphql.query.definitions_count", parsed.definitions.length);
2407
+ span.addEvent("query-parsing-completed");
2408
+ }
2409
+ return { parsed, error: null };
2410
+ } catch (error) {
2411
+ if (span) {
2412
+ span.setAttribute("graphql.query.parsed", false);
2413
+ span.setAttribute("graphql.query.error", error.message);
2414
+ }
2415
+ return { parsed: null, error: error.message };
2416
+ }
2417
+ }
2418
+ );
2419
+ return instrumentedParseQuery(name, queryString, telemetry);
2420
+ }
2421
+ /**
2422
+ * Validates GraphQL query with OpenTelemetry instrumentation
2423
+ *
2424
+ * This method wraps the query validation logic with distributed tracing instrumentation,
2425
+ * creating a span that tracks validation metrics and results in New Relic.
2426
+ *
2427
+ * @param name - Action name used for span naming
2428
+ * @param schema - GraphQL schema to validate against
2429
+ * @param parsedQuery - Parsed GraphQL query document
2430
+ * @param params - Request parameters for telemetry context
2431
+ * @returns Array of validation errors (empty if valid)
2432
+ *
2433
+ * @private
2434
+ */
2435
+ static validateQueryWithInstrumentation(name, schema, parsedQuery, telemetry) {
2436
+ const instrumentedValidateQuery = telemetry.instrument(
2437
+ `graphql.action.${name}.validate-query`,
2438
+ (actionName, actionSchema, actionParsedQuery, actionTelemetry) => {
2439
+ const span = actionTelemetry.getCurrentSpan();
2440
+ if (span) {
2441
+ span.addEvent("query-validation-started");
2442
+ }
2443
+ const errors = validate(actionSchema, actionParsedQuery);
2444
+ if (span) {
2445
+ span.setAttribute("graphql.query.valid", errors.length === 0);
2446
+ span.setAttribute("graphql.query.validation_errors_count", errors.length);
2447
+ if (errors.length > 0) {
2448
+ span.setAttribute(
2449
+ "graphql.query.validation_errors",
2450
+ errors.map((e) => e.message).join("; ")
2451
+ );
2452
+ }
2453
+ span.addEvent("query-validation-completed", {
2454
+ valid: errors.length === 0,
2455
+ errorCount: errors.length
2456
+ });
2457
+ }
2458
+ return errors;
2459
+ }
2460
+ );
2461
+ return instrumentedValidateQuery(name, schema, parsedQuery, telemetry);
2462
+ }
2463
+ /**
2464
+ * Checks for introspection query with OpenTelemetry instrumentation
2465
+ *
2466
+ * This method wraps the introspection detection logic with distributed tracing instrumentation,
2467
+ * creating a span that tracks whether an introspection query was detected in New Relic.
2468
+ *
2469
+ * @param name - Action name used for span naming
2470
+ * @param parsedQuery - Parsed GraphQL query document
2471
+ * @param params - Request parameters for telemetry context
2472
+ * @returns True if introspection query detected, false otherwise
2473
+ *
2474
+ * @private
2475
+ */
2476
+ static checkIntrospectionWithInstrumentation(name, parsedQuery, telemetry) {
2477
+ const instrumentedIntrospectionCheck = telemetry.instrument(
2478
+ `graphql.action.${name}.introspection-check`,
2479
+ (actionName, actionParsedQuery, actionTelemetry) => {
2480
+ const span = actionTelemetry.getCurrentSpan();
2481
+ if (span) {
2482
+ span.setAttribute("graphql.introspection.disabled", true);
2483
+ span.addEvent("introspection-check-started");
2484
+ }
2485
+ const isIntrospection = actionParsedQuery.definitions.some(
2486
+ (definition) => definition.selectionSet.selections.some(
2487
+ (selection) => selection.name.value.startsWith("__")
2488
+ )
2489
+ );
2490
+ if (span) {
2491
+ span.setAttribute("graphql.introspection.detected", isIntrospection);
2492
+ span.addEvent("introspection-check-completed", {
2493
+ detected: isIntrospection
2494
+ });
2495
+ }
2496
+ return isIntrospection;
2497
+ }
2498
+ );
2499
+ return instrumentedIntrospectionCheck(name, parsedQuery, telemetry);
2500
+ }
2501
+ /**
2502
+ * Executes GraphQL query with OpenTelemetry instrumentation
2503
+ *
2504
+ * This method wraps the GraphQL execution with distributed tracing instrumentation,
2505
+ * creating a span that tracks execution metrics and results in New Relic.
2506
+ *
2507
+ * @param name - Action name used for span naming
2508
+ * @param executionParams - GraphQL execution parameters (schema, source, rootValue, etc.)
2509
+ * @param params - Request parameters for telemetry context
2510
+ * @returns Promise resolving to the GraphQL execution result
2511
+ *
2512
+ * @private
2513
+ */
2514
+ static async executeGraphQLWithInstrumentation(name, executionParams, telemetry) {
2515
+ const instrumentedGraphQLExecution = telemetry.instrument(
2516
+ `graphql.action.${name}.execute`,
2517
+ async (actionName, actionExecutionParams, actionTelemetry) => {
2518
+ const span = actionTelemetry.getCurrentSpan();
2519
+ if (span) {
2520
+ span.setAttribute(
2521
+ "graphql.execution.operation_name",
2522
+ actionExecutionParams.operationName || "default"
2523
+ );
2524
+ span.setAttribute(
2525
+ "graphql.execution.has_variables",
2526
+ !!actionExecutionParams.variableValues
2527
+ );
2528
+ span.addEvent("graphql-execution-started");
2529
+ }
2530
+ const result = await graphql(actionExecutionParams);
2531
+ if (span) {
2532
+ span.setAttribute("graphql.execution.has_errors", !!result.errors);
2533
+ if (result.errors) {
2534
+ span.setAttribute("graphql.execution.errors_count", result.errors.length);
2535
+ span.setAttribute(
2536
+ "graphql.execution.errors",
2537
+ result.errors.map((e) => e.message).join("; ")
2538
+ );
2539
+ }
2540
+ span.setAttribute("graphql.execution.has_data", !!result.data);
2541
+ span.addEvent("graphql-execution-completed", {
2542
+ hasErrors: !!result.errors,
2543
+ hasData: !!result.data
2544
+ });
2545
+ }
2546
+ return result;
2547
+ }
2548
+ );
2549
+ return instrumentedGraphQLExecution(name, executionParams, telemetry);
1893
2550
  }
1894
2551
  };
1895
2552
  __name(_GraphQlAction, "GraphQlAction");
@@ -1954,9 +2611,15 @@ var _OpenwhiskAction = class _OpenwhiskAction {
1954
2611
  * ```typescript
1955
2612
  * const handler = OpenwhiskAction.execute(
1956
2613
  * 'processWebhook',
1957
- * async (params, { logger, headers }) => {
2614
+ * async (params, { logger, headers, telemetry }) => {
1958
2615
  * logger.info({ message: 'Webhook received', event: params.event });
1959
2616
  *
2617
+ * // Access current span for custom instrumentation
2618
+ * const span = telemetry.getCurrentSpan();
2619
+ * if (span) {
2620
+ * span.setAttribute('webhook.event', params.event);
2621
+ * }
2622
+ *
1960
2623
  * // Your webhook processing logic here
1961
2624
  * const result = await processWebhookData(params);
1962
2625
  *
@@ -1971,9 +2634,10 @@ var _OpenwhiskAction = class _OpenwhiskAction {
1971
2634
  static execute(name = "main", action = async (_params) => {
1972
2635
  return { statusCode: 200 /* OK */, body: {} };
1973
2636
  }) {
2637
+ const telemetry = new telemetry_default();
1974
2638
  const openwhiskAction = /* @__PURE__ */ __name(async (params) => {
1975
2639
  params.action_type = "openwhisk-action";
1976
- const logger = telemetry_default.createLogger(name, params);
2640
+ const logger = telemetry.createLogger(name);
1977
2641
  try {
1978
2642
  logger.debug({
1979
2643
  message: "OpenWhisk action execution started"
@@ -1982,7 +2646,14 @@ var _OpenwhiskAction = class _OpenwhiskAction {
1982
2646
  message: "OpenWhisk action parameters received",
1983
2647
  params
1984
2648
  });
1985
- const result = await action(params, { logger, headers: params.__ow_headers || {} });
2649
+ const result = await _OpenwhiskAction.executeActionWithInstrumentation(
2650
+ name,
2651
+ params.__ow_headers || {},
2652
+ params,
2653
+ action,
2654
+ telemetry,
2655
+ logger
2656
+ );
1986
2657
  logger.debug({
1987
2658
  message: "OpenWhisk action execution completed",
1988
2659
  result
@@ -2004,7 +2675,51 @@ var _OpenwhiskAction = class _OpenwhiskAction {
2004
2675
  return response_default.error(500 /* INTERNAL_ERROR */, "server error");
2005
2676
  }
2006
2677
  }, "openwhiskAction");
2007
- return telemetry_default.initialize(openwhiskAction);
2678
+ return telemetry.initialize(openwhiskAction);
2679
+ }
2680
+ /**
2681
+ * Executes OpenWhisk action with OpenTelemetry instrumentation
2682
+ *
2683
+ * This method wraps the action execution with distributed tracing instrumentation,
2684
+ * creating a span that tracks execution metrics and results in New Relic.
2685
+ *
2686
+ * @param name - Action name used for span naming
2687
+ * @param headers - Request headers
2688
+ * @param params - Request parameters
2689
+ * @param action - The user's action function to execute
2690
+ * @param telemetry - Telemetry instance for instrumentation
2691
+ * @param logger - Logger instance
2692
+ * @returns Promise resolving to the action result
2693
+ *
2694
+ * @private
2695
+ */
2696
+ static async executeActionWithInstrumentation(name, headers, params, action, telemetry, logger) {
2697
+ const instrumentedAction = telemetry.instrument(
2698
+ `openwhisk.action.${name}.execute`,
2699
+ async (actionName, actionHeaders, actionParams, actionFn, actionTelemetry, actionLogger) => {
2700
+ const span = actionTelemetry.getCurrentSpan();
2701
+ if (span) {
2702
+ span.setAttribute("openwhisk.action.name", actionName);
2703
+ span.setAttribute("openwhisk.action.has_headers", !!actionHeaders);
2704
+ span.addEvent("openwhisk-execution-started");
2705
+ }
2706
+ const result = await actionFn(actionParams, {
2707
+ logger: actionLogger,
2708
+ headers: actionHeaders,
2709
+ telemetry: actionTelemetry
2710
+ });
2711
+ if (span) {
2712
+ const statusCode = "statusCode" in result ? result.statusCode : result.error.statusCode;
2713
+ span.setAttribute("openwhisk.action.response_status", statusCode);
2714
+ span.setAttribute("openwhisk.action.has_error", "error" in result);
2715
+ span.addEvent("openwhisk-execution-completed", {
2716
+ statusCode
2717
+ });
2718
+ }
2719
+ return result;
2720
+ }
2721
+ );
2722
+ return instrumentedAction(name, headers, params, action, telemetry, logger);
2008
2723
  }
2009
2724
  };
2010
2725
  __name(_OpenwhiskAction, "OpenwhiskAction");
@@ -2528,41 +3243,17 @@ var _WebhookAction = class _WebhookAction {
2528
3243
  */
2529
3244
  static execute(name = "webhook", requiredParams = [], requiredHeaders = [], signatureVerification = "disabled" /* DISABLED */, action = async () => response_default2.success()) {
2530
3245
  const httpMethods = ["POST" /* POST */];
2531
- const verifySignature = /* @__PURE__ */ __name(async (params) => {
2532
- const signature = params.__ow_headers["x-adobe-commerce-webhook-signature"] || "";
2533
- if (!signature) {
2534
- return "Header `x-adobe-commerce-webhook-signature` not found. Make sure Webhooks signature is enabled in the Commerce instance.";
2535
- }
2536
- const body = params.__ow_body;
2537
- if (!body) {
2538
- return "Request body not found. Make sure the action is configured with `raw-http: true`.";
2539
- }
2540
- let publicKey = params.PUBLIC_KEY;
2541
- if (!publicKey && params.PUBLIC_KEY_BASE64) {
2542
- publicKey = atob(params.PUBLIC_KEY_BASE64);
2543
- }
2544
- if (!publicKey) {
2545
- return "Public key not found. Make sure the action is configured with the input `PUBLIC_KEY` or `PUBLIC_KEY_BASE64` and it is defined in .env file.";
2546
- }
2547
- try {
2548
- const verifier = crypto.createVerify("SHA256");
2549
- verifier.update(body);
2550
- const isSignatureValid = verifier.verify(publicKey, signature, "base64");
2551
- if (!isSignatureValid) {
2552
- return "The signature is invalid.";
2553
- }
2554
- } catch (error) {
2555
- return "The signature is invalid.";
2556
- }
2557
- return null;
2558
- }, "verifySignature");
2559
3246
  const callback = /* @__PURE__ */ __name(async (params, ctx) => {
2560
- const { logger } = ctx;
3247
+ const { logger, telemetry } = ctx;
2561
3248
  if (signatureVerification === "enabled" /* ENABLED */) {
2562
- const verificationErrorMessage = await verifySignature(params);
3249
+ const verificationErrorMessage = await _WebhookAction.verifySignatureWithInstrumentation(
3250
+ name,
3251
+ params,
3252
+ telemetry
3253
+ );
2563
3254
  if (verificationErrorMessage) {
2564
3255
  logger.error({
2565
- message: "Webhook actionsignature verification failed",
3256
+ message: "Webhook action signature verification failed",
2566
3257
  error: verificationErrorMessage
2567
3258
  });
2568
3259
  const verificationErrorResponse = response_default2.exception(verificationErrorMessage);
@@ -2573,7 +3264,13 @@ var _WebhookAction = class _WebhookAction {
2573
3264
  ...JSON.parse(atob(params.__ow_body))
2574
3265
  };
2575
3266
  }
2576
- const errorMessage = validator_default.checkMissingRequestInputs(params, requiredParams, requiredHeaders) ?? "";
3267
+ const errorMessage = _WebhookAction.validateWithInstrumentation(
3268
+ name,
3269
+ params,
3270
+ requiredParams,
3271
+ requiredHeaders,
3272
+ telemetry
3273
+ );
2577
3274
  if (errorMessage) {
2578
3275
  logger.error({
2579
3276
  message: "Webhook action validation failed",
@@ -2582,13 +3279,183 @@ var _WebhookAction = class _WebhookAction {
2582
3279
  const errorMessageResponse = response_default2.exception(errorMessage);
2583
3280
  return response_default.success(JSON.stringify(errorMessageResponse));
2584
3281
  }
2585
- const response = await action(params, ctx);
3282
+ const response = await _WebhookAction.executeActionWithInstrumentation(
3283
+ name,
3284
+ action,
3285
+ params,
3286
+ ctx
3287
+ );
2586
3288
  return response_default.success(JSON.stringify(response));
2587
3289
  }, "callback");
2588
3290
  runtime_action_default.setActionType("webhook-action");
2589
3291
  runtime_action_default.setActionTypeName("Webhook action");
2590
3292
  return runtime_action_default.execute(name, httpMethods, [], [], callback);
2591
3293
  }
3294
+ /**
3295
+ * Executes webhook action with OpenTelemetry instrumentation
3296
+ *
3297
+ * This method wraps the webhook action execution with distributed tracing instrumentation,
3298
+ * creating a span that tracks execution metrics and results in New Relic.
3299
+ *
3300
+ * @param name - Webhook action name used for span naming
3301
+ * @param action - The webhook action function to execute
3302
+ * @param params - Request parameters
3303
+ * @param ctx - Context object with logger, headers, and telemetry
3304
+ * @returns Promise resolving to the webhook action response(s)
3305
+ *
3306
+ * @private
3307
+ */
3308
+ static async executeActionWithInstrumentation(name, action, params, ctx) {
3309
+ const instrumentedWebhookAction = ctx.telemetry.instrument(
3310
+ `webhook.action.${name}.execute`,
3311
+ async (actionName, actionFn, actionParams, context2) => {
3312
+ const span = context2.telemetry.getCurrentSpan();
3313
+ if (span) {
3314
+ span.setAttribute("webhook.action.name", actionName);
3315
+ span.setAttribute("webhook.action.has_headers", !!context2.headers);
3316
+ span.addEvent("webhook-execution-started");
3317
+ }
3318
+ const response = await actionFn(actionParams, context2);
3319
+ if (span) {
3320
+ span.setAttribute("webhook.action.response_is_array", Array.isArray(response));
3321
+ if (Array.isArray(response)) {
3322
+ span.setAttribute("webhook.action.response_count", response.length);
3323
+ }
3324
+ span.addEvent("webhook-execution-completed", {
3325
+ isArray: Array.isArray(response),
3326
+ count: Array.isArray(response) ? response.length : 1
3327
+ });
3328
+ }
3329
+ return response;
3330
+ }
3331
+ );
3332
+ return instrumentedWebhookAction(name, action, params, ctx);
3333
+ }
3334
+ /**
3335
+ * Verifies webhook signature with OpenTelemetry instrumentation
3336
+ *
3337
+ * This method wraps the signature verification logic with distributed tracing instrumentation,
3338
+ * creating a span that tracks verification metrics and results in New Relic.
3339
+ *
3340
+ * @param name - Webhook action name used for span naming
3341
+ * @param params - Request parameters including headers, body, and public key
3342
+ * @param telemetry - Telemetry instance for instrumentation
3343
+ * @returns Error message if verification fails, null if successful
3344
+ *
3345
+ * @private
3346
+ */
3347
+ static async verifySignatureWithInstrumentation(name, params, telemetry) {
3348
+ const instrumentedVerifySignature = telemetry.instrument(
3349
+ `webhook.action.${name}.verify-signature`,
3350
+ async (actionName, actionParams, actionTelemetry) => {
3351
+ const span = actionTelemetry.getCurrentSpan();
3352
+ if (span) {
3353
+ span.setAttribute("webhook.signature.enabled", true);
3354
+ span.setAttribute(
3355
+ "webhook.signature.header_present",
3356
+ !!actionParams.__ow_headers?.["x-adobe-commerce-webhook-signature"]
3357
+ );
3358
+ span.setAttribute("webhook.signature.has_body", !!actionParams.__ow_body);
3359
+ span.setAttribute(
3360
+ "webhook.signature.has_public_key",
3361
+ !!(actionParams.PUBLIC_KEY || actionParams.PUBLIC_KEY_BASE64)
3362
+ );
3363
+ span.addEvent("signature-verification-started");
3364
+ }
3365
+ const verificationError = await _WebhookAction.verifySignature(actionParams);
3366
+ if (span) {
3367
+ span.setAttribute("webhook.signature.valid", verificationError === null);
3368
+ if (verificationError) {
3369
+ span.setAttribute("webhook.signature.error", verificationError);
3370
+ }
3371
+ span.addEvent("signature-verification-completed", {
3372
+ valid: verificationError === null
3373
+ });
3374
+ }
3375
+ return verificationError;
3376
+ }
3377
+ );
3378
+ return instrumentedVerifySignature(name, params, telemetry);
3379
+ }
3380
+ /**
3381
+ * Validates webhook parameters and headers with OpenTelemetry instrumentation
3382
+ *
3383
+ * This method wraps the validation logic with distributed tracing instrumentation,
3384
+ * creating a span that tracks validation metrics and results in New Relic.
3385
+ *
3386
+ * @param name - Webhook action name used for span naming
3387
+ * @param params - Request parameters
3388
+ * @param requiredParams - List of required parameter names to validate
3389
+ * @param requiredHeaders - List of required header names to validate
3390
+ * @param telemetry - Telemetry instance for instrumentation
3391
+ * @returns Error message if validation fails, empty string if successful
3392
+ *
3393
+ * @private
3394
+ */
3395
+ static validateWithInstrumentation(name, params, requiredParams, requiredHeaders, telemetry) {
3396
+ const instrumentedValidate = telemetry.instrument(
3397
+ `webhook.action.${name}.validate`,
3398
+ (actionName, actionParams, actionRequiredParams, actionRequiredHeaders, actionTelemetry) => {
3399
+ const span = actionTelemetry.getCurrentSpan();
3400
+ if (span) {
3401
+ span.setAttribute("webhook.validation.required_params", actionRequiredParams.join(","));
3402
+ span.setAttribute("webhook.validation.required_headers", actionRequiredHeaders.join(","));
3403
+ }
3404
+ const errorMessage = validator_default.checkMissingRequestInputs(
3405
+ actionParams,
3406
+ actionRequiredParams,
3407
+ actionRequiredHeaders
3408
+ ) ?? "";
3409
+ if (span) {
3410
+ span.setAttribute("webhook.validation.passed", errorMessage === "");
3411
+ if (errorMessage) {
3412
+ span.setAttribute("webhook.validation.error", errorMessage);
3413
+ }
3414
+ }
3415
+ return errorMessage;
3416
+ }
3417
+ );
3418
+ return instrumentedValidate(name, params, requiredParams, requiredHeaders, telemetry);
3419
+ }
3420
+ /**
3421
+ * Verify webhook signature using a public key and SHA256
3422
+ *
3423
+ * This method validates the authenticity of webhook requests by verifying
3424
+ * the signature against the request body using the provided public key.
3425
+ *
3426
+ * @param params - Request parameters including headers, body, and public key
3427
+ * @returns Error message if verification fails, null if successful
3428
+ *
3429
+ * @private
3430
+ */
3431
+ static async verifySignature(params) {
3432
+ const signature = params.__ow_headers["x-adobe-commerce-webhook-signature"] || "";
3433
+ if (!signature) {
3434
+ return "Header `x-adobe-commerce-webhook-signature` not found. Make sure Webhooks signature is enabled in the Commerce instance.";
3435
+ }
3436
+ const body = params.__ow_body;
3437
+ if (!body) {
3438
+ return "Request body not found. Make sure the action is configured with `raw-http: true`.";
3439
+ }
3440
+ let publicKey = params.PUBLIC_KEY;
3441
+ if (!publicKey && params.PUBLIC_KEY_BASE64) {
3442
+ publicKey = atob(params.PUBLIC_KEY_BASE64);
3443
+ }
3444
+ if (!publicKey) {
3445
+ return "Public key not found. Make sure the action is configured with the input `PUBLIC_KEY` or `PUBLIC_KEY_BASE64` and it is defined in .env file.";
3446
+ }
3447
+ try {
3448
+ const verifier = crypto.createVerify("SHA256");
3449
+ verifier.update(body);
3450
+ const isSignatureValid = verifier.verify(publicKey, signature, "base64");
3451
+ if (!isSignatureValid) {
3452
+ return "The signature is invalid.";
3453
+ }
3454
+ } catch (error) {
3455
+ return "The signature is invalid.";
3456
+ }
3457
+ return null;
3458
+ }
2592
3459
  };
2593
3460
  __name(_WebhookAction, "WebhookAction");
2594
3461
  var WebhookAction = _WebhookAction;