@adobe-commerce/aio-toolkit 1.0.12 → 1.0.14

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 {
@@ -1490,6 +1594,23 @@ var _RuntimeAction = class _RuntimeAction {
1490
1594
  static getActionType() {
1491
1595
  return _RuntimeAction.actionType;
1492
1596
  }
1597
+ /**
1598
+ * Sets the action type name for the next action execution
1599
+ * This is used for logging to identify different action types
1600
+ * (runtime-action, webhook-action, event-consumer-action, etc.)
1601
+ *
1602
+ * @param name - The action type name identifier
1603
+ */
1604
+ static setActionTypeName(name) {
1605
+ _RuntimeAction.actionTypeName = name;
1606
+ }
1607
+ /**
1608
+ * Gets the current action type name
1609
+ * @returns The current action type name identifier
1610
+ */
1611
+ static getActionTypeName() {
1612
+ return _RuntimeAction.actionTypeName;
1613
+ }
1493
1614
  /**
1494
1615
  * Creates a runtime action handler with validation, logging, and telemetry
1495
1616
  *
@@ -1507,7 +1628,7 @@ var _RuntimeAction = class _RuntimeAction {
1507
1628
  * @param requiredHeaders - Required header names (case-insensitive)
1508
1629
  * @param action - User-defined action function that receives:
1509
1630
  * - params: All request parameters including __ow_* runtime parameters
1510
- * - ctx: Context object with logger and headers
1631
+ * - ctx: Context object with logger, headers, and telemetry
1511
1632
  * @returns Wrapped action function ready to be exported as Adobe I/O Runtime entrypoint
1512
1633
  *
1513
1634
  * @example With All Options
@@ -1517,10 +1638,16 @@ var _RuntimeAction = class _RuntimeAction {
1517
1638
  * [HttpMethod.POST],
1518
1639
  * ['orderId', 'customerId'],
1519
1640
  * ['authorization', 'x-api-key'],
1520
- * async (params, { logger, headers }) => {
1641
+ * async (params, { logger, headers, telemetry }) => {
1521
1642
  * const { orderId, customerId } = params;
1522
1643
  * logger.info({ message: 'Processing order', orderId, customerId });
1523
1644
  *
1645
+ * // Access current span for custom instrumentation
1646
+ * const span = telemetry.getCurrentSpan();
1647
+ * if (span) {
1648
+ * span.setAttribute('order.id', orderId);
1649
+ * }
1650
+ *
1524
1651
  * // Your business logic here
1525
1652
  * const result = await processOrderLogic(orderId, customerId);
1526
1653
  *
@@ -1543,58 +1670,174 @@ var _RuntimeAction = class _RuntimeAction {
1543
1670
  static execute(name = "main", httpMethods = [], requiredParams = [], requiredHeaders = [], action = async (_params) => {
1544
1671
  return { statusCode: 200 /* OK */, body: {} };
1545
1672
  }) {
1673
+ const telemetry = new telemetry_default();
1546
1674
  const runtimeAction = /* @__PURE__ */ __name(async (params) => {
1547
1675
  if (!params.action_type) {
1548
1676
  params.action_type = _RuntimeAction.getActionType();
1549
1677
  }
1550
- const logger = telemetry_default.createLogger(name, params);
1678
+ telemetry.setParams(params);
1679
+ const logger = telemetry.createLogger(name);
1551
1680
  try {
1552
1681
  logger.debug({
1553
- message: `${name}-started`,
1554
- action_name: name
1555
- });
1556
- logger.debug({
1557
- message: `${name}-headers`,
1558
- headers: params.__ow_headers || {}
1559
- });
1560
- logger.debug({
1561
- message: `${name}-body`,
1562
- body: params.__ow_body || {}
1682
+ message: `${_RuntimeAction.getActionTypeName()} execution started`
1563
1683
  });
1564
- const validationError = _RuntimeAction.validateRequest(
1684
+ logger.debug(
1685
+ JSON.stringify({
1686
+ message: `${_RuntimeAction.getActionTypeName()} headers received`,
1687
+ headers: params.__ow_headers || {}
1688
+ })
1689
+ );
1690
+ logger.debug(
1691
+ JSON.stringify({
1692
+ message: `${_RuntimeAction.getActionTypeName()} body received`,
1693
+ body: params.__ow_body || {}
1694
+ })
1695
+ );
1696
+ const validationError = _RuntimeAction.validateRequestWithInstrumentation(
1697
+ name,
1565
1698
  params,
1566
1699
  requiredParams,
1567
1700
  requiredHeaders,
1568
1701
  httpMethods,
1569
- logger,
1570
- name
1702
+ telemetry,
1703
+ logger
1571
1704
  );
1572
1705
  if (validationError) {
1573
1706
  return validationError;
1574
1707
  }
1575
- const result = await action(params, { logger, headers: params.__ow_headers || {} });
1576
- logger.debug({
1577
- message: `${name}-completed`,
1578
- result
1579
- });
1708
+ const result = await _RuntimeAction.executeActionWithInstrumentation(
1709
+ name,
1710
+ params.__ow_headers || {},
1711
+ params,
1712
+ action,
1713
+ telemetry,
1714
+ logger
1715
+ );
1716
+ logger.debug(
1717
+ JSON.stringify({
1718
+ message: `${_RuntimeAction.getActionTypeName()} execution completed`,
1719
+ result
1720
+ })
1721
+ );
1580
1722
  return result;
1581
1723
  } catch (error) {
1582
1724
  if (error instanceof Error) {
1583
1725
  logger.error({
1584
- message: `${name}-failed`,
1726
+ message: `${_RuntimeAction.getActionTypeName()} execution failed`,
1585
1727
  error: error.message,
1586
1728
  stack: error.stack
1587
1729
  });
1588
1730
  } else {
1589
1731
  logger.error({
1590
- message: `${name}-failed`,
1732
+ message: `${_RuntimeAction.getActionTypeName()} execution failed`,
1591
1733
  error
1592
1734
  });
1593
1735
  }
1594
1736
  return response_default.error(500 /* INTERNAL_ERROR */, "server error");
1595
1737
  }
1596
1738
  }, "runtimeAction");
1597
- 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
+ );
1598
1841
  }
1599
1842
  /**
1600
1843
  * Validates incoming request against required parameters, headers, and HTTP methods
@@ -1604,12 +1847,12 @@ var _RuntimeAction = class _RuntimeAction {
1604
1847
  * 2. Checks for missing required headers (case-insensitive)
1605
1848
  * 3. Validates the HTTP method against allowed methods list
1606
1849
  *
1850
+ * @param _name - Action name for logging (currently unused)
1607
1851
  * @param params - Request parameters including __ow_* runtime parameters
1608
1852
  * @param requiredParams - List of required parameter names to validate
1609
1853
  * @param requiredHeaders - List of required header names to validate (case-insensitive)
1610
1854
  * @param httpMethods - Allowed HTTP methods. Empty array skips HTTP method validation
1611
1855
  * @param logger - Logger instance for error logging (child logger with request ID if present)
1612
- * @param name - Action name for logging
1613
1856
  * @returns RuntimeActionResponseType with error details if validation fails, null if valid
1614
1857
  *
1615
1858
  * @private
@@ -1629,11 +1872,11 @@ var _RuntimeAction = class _RuntimeAction {
1629
1872
  * }
1630
1873
  * ```
1631
1874
  */
1632
- static validateRequest(params, requiredParams, requiredHeaders, httpMethods, logger, name) {
1875
+ static validateRequest(_name, params, requiredParams, requiredHeaders, httpMethods, logger) {
1633
1876
  const errorMessage = validator_default.checkMissingRequestInputs(params, requiredParams, requiredHeaders) ?? "";
1634
1877
  if (errorMessage) {
1635
1878
  logger.error({
1636
- message: `${name}-validation-failed`,
1879
+ message: `${_RuntimeAction.getActionTypeName()} validation failed`,
1637
1880
  error: errorMessage
1638
1881
  });
1639
1882
  return response_default.error(400 /* BAD_REQUEST */, errorMessage);
@@ -1642,7 +1885,7 @@ var _RuntimeAction = class _RuntimeAction {
1642
1885
  if (httpMethods.length > 0 && !httpMethods.includes(requestMethod)) {
1643
1886
  const errorMessage2 = `Invalid HTTP method: ${params.__ow_method}. Allowed methods are: ${httpMethods.join(", ")}`;
1644
1887
  logger.error({
1645
- message: `${name}-validation-failed`,
1888
+ message: `${_RuntimeAction.getActionTypeName()} validation failed`,
1646
1889
  error: errorMessage2
1647
1890
  });
1648
1891
  return response_default.error(405 /* METHOD_NOT_ALLOWED */, errorMessage2);
@@ -1656,6 +1899,11 @@ __name(_RuntimeAction, "RuntimeAction");
1656
1899
  * Can be set using setActionType() before calling execute()
1657
1900
  */
1658
1901
  _RuntimeAction.actionType = "runtime-action";
1902
+ /**
1903
+ * Private static property to store the action type name for logging
1904
+ * Can be set using setActionTypeName() before calling execute()
1905
+ */
1906
+ _RuntimeAction.actionTypeName = "Runtime action";
1659
1907
  var RuntimeAction = _RuntimeAction;
1660
1908
  var runtime_action_default = RuntimeAction;
1661
1909
 
@@ -1704,11 +1952,15 @@ var _EventConsumerAction = class _EventConsumerAction {
1704
1952
  * 'webhook-processor',
1705
1953
  * ['eventType', 'eventData'],
1706
1954
  * ['x-webhook-signature'],
1707
- * async (params, { logger, headers }) => {
1955
+ * async (params, { logger, headers, telemetry }) => {
1708
1956
  * logger.info({
1709
1957
  * message: 'Processing webhook',
1710
1958
  * eventType: params.eventType
1711
1959
  * });
1960
+ * const span = telemetry.getCurrentSpan();
1961
+ * if (span) {
1962
+ * span.setAttribute('event.type', params.eventType);
1963
+ * }
1712
1964
  * // Process event...
1713
1965
  * return { statusCode: 200, body: { processed: true } };
1714
1966
  * }
@@ -1718,49 +1970,155 @@ var _EventConsumerAction = class _EventConsumerAction {
1718
1970
  static execute(name = "main", requiredParams = [], requiredHeaders = [], action = async (_params) => {
1719
1971
  return { statusCode: 200 /* OK */, body: {} };
1720
1972
  }) {
1973
+ const telemetry = new telemetry_default();
1721
1974
  const eventConsumerAction = /* @__PURE__ */ __name(async (params) => {
1722
1975
  params.action_type = "event-consumer-action";
1723
- const logger = telemetry_default.createLogger(name, params);
1976
+ const logger = telemetry.createLogger(name);
1724
1977
  try {
1725
1978
  logger.debug({
1726
- message: `${name}-started`,
1727
- action_name: name
1979
+ message: "Event consumer action execution started"
1728
1980
  });
1729
1981
  logger.debug({
1730
- message: `${name}-headers`,
1982
+ message: "Event consumer action headers received",
1731
1983
  headers: params.__ow_headers || {}
1732
1984
  });
1733
1985
  logger.debug({
1734
- message: `${name}-parameters`,
1986
+ message: "Event consumer action parameters received",
1735
1987
  parameters: params
1736
1988
  });
1737
- const errorMessage = validator_default.checkMissingRequestInputs(params, requiredParams, requiredHeaders) || "";
1989
+ const errorMessage = _EventConsumerAction.validateWithInstrumentation(
1990
+ name,
1991
+ params,
1992
+ requiredParams,
1993
+ requiredHeaders,
1994
+ telemetry
1995
+ );
1738
1996
  if (errorMessage) {
1739
1997
  logger.error({
1740
- message: `${name}-validation-failed`,
1998
+ message: "Event consumer action validation failed",
1741
1999
  error: errorMessage
1742
2000
  });
1743
2001
  return response_default.error(400 /* BAD_REQUEST */, errorMessage);
1744
2002
  }
1745
- 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
+ );
1746
2011
  logger.debug({
1747
- message: `${name}-completed`,
2012
+ message: "Event consumer action execution completed",
1748
2013
  result
1749
2014
  });
1750
2015
  return result;
1751
2016
  } catch (error) {
1752
2017
  if (error instanceof Error) {
1753
2018
  logger.error({
2019
+ message: "Event consumer action execution failed",
1754
2020
  error: error.message,
1755
2021
  stack: error.stack
1756
2022
  });
1757
2023
  } else {
1758
- logger.error({ error });
2024
+ logger.error({
2025
+ message: "Event consumer action execution failed",
2026
+ error
2027
+ });
1759
2028
  }
1760
2029
  return response_default.error(500 /* INTERNAL_ERROR */, "server error");
1761
2030
  }
1762
2031
  }, "eventConsumerAction");
1763
- 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);
1764
2122
  }
1765
2123
  };
1766
2124
  __name(_EventConsumerAction, "EventConsumerAction");
@@ -1770,6 +2128,116 @@ var event_consumer_action_default = EventConsumerAction;
1770
2128
  // src/framework/graphql-action/index.ts
1771
2129
  import { graphql, buildSchema, parse, validate } from "graphql";
1772
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
+ */
1773
2241
  static execute(schema = `
1774
2242
  type Query {
1775
2243
  hello: String
@@ -1779,65 +2247,306 @@ var _GraphQlAction = class _GraphQlAction {
1779
2247
  hello: /* @__PURE__ */ __name(() => "Hello World!", "hello")
1780
2248
  };
1781
2249
  }, name = "main", disableIntrospection = false) {
2250
+ const callback = /* @__PURE__ */ __name(async (params, ctx) => {
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);
2255
+ }
2256
+ const graphqlResolvers = await resolvers({
2257
+ ...ctx,
2258
+ ...{
2259
+ params
2260
+ }
2261
+ });
2262
+ const context2 = {};
2263
+ const query = params.query;
2264
+ const { parsed: parsedQuery, error: parseError } = _GraphQlAction.parseQueryWithInstrumentation(name, query, telemetry);
2265
+ if (parseError) {
2266
+ return response_default.error(400 /* BAD_REQUEST */, parseError);
2267
+ }
2268
+ logger.debug(
2269
+ JSON.stringify({
2270
+ message: "GraphQL action query parsed",
2271
+ query: parsedQuery
2272
+ })
2273
+ );
2274
+ const validationErrors = _GraphQlAction.validateQueryWithInstrumentation(
2275
+ name,
2276
+ graphqlSchema,
2277
+ parsedQuery,
2278
+ telemetry
2279
+ );
2280
+ if (validationErrors.length) {
2281
+ logger.error({
2282
+ message: "GraphQL action query validation failed",
2283
+ errors: validationErrors
2284
+ });
2285
+ return response_default.error(
2286
+ 400 /* BAD_REQUEST */,
2287
+ validationErrors.map((err) => err.message).join(", ")
2288
+ );
2289
+ }
2290
+ if (disableIntrospection) {
2291
+ logger.debug({
2292
+ message: "GraphQL action introspection check disabled"
2293
+ });
2294
+ const isIntrospectionQuery = _GraphQlAction.checkIntrospectionWithInstrumentation(
2295
+ name,
2296
+ parsedQuery,
2297
+ telemetry
2298
+ );
2299
+ if (isIntrospectionQuery) {
2300
+ logger.error({
2301
+ message: "GraphQL action introspection query detected",
2302
+ query: parsedQuery
2303
+ });
2304
+ return response_default.error(
2305
+ 400 /* BAD_REQUEST */,
2306
+ "Introspection is disabled for security reasons."
2307
+ );
2308
+ }
2309
+ }
2310
+ const variables = typeof params.variables === "string" ? JSON.parse(params.variables) : params.variables;
2311
+ logger.debug({
2312
+ message: "GraphQL action variables parsed",
2313
+ variables
2314
+ });
2315
+ return response_default.success(
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
+ )
2328
+ );
2329
+ }, "callback");
2330
+ runtime_action_default.setActionType("graphql-action");
2331
+ runtime_action_default.setActionTypeName("GraphQL action");
1782
2332
  return runtime_action_default.execute(
1783
2333
  `graphql-${name}`,
1784
2334
  ["GET" /* GET */, "POST" /* POST */],
1785
2335
  ["query"],
1786
2336
  [],
1787
- async (params, ctx) => {
1788
- let graphqlSchema;
2337
+ callback
2338
+ );
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
+ }
1789
2362
  try {
1790
- graphqlSchema = buildSchema(schema);
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 };
1791
2369
  } catch (error) {
1792
- return response_default.error(400 /* BAD_REQUEST */, error.message);
1793
- }
1794
- const graphqlResolvers = await resolvers({
1795
- ...ctx,
1796
- ...{
1797
- params
2370
+ if (span) {
2371
+ span.setAttribute("graphql.schema.built", false);
2372
+ span.setAttribute("graphql.schema.error", error.message);
1798
2373
  }
1799
- });
1800
- const context2 = {};
1801
- const query = params.query;
1802
- let parsedQuery;
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
+ }
1803
2402
  try {
1804
- parsedQuery = parse(query);
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 };
1805
2410
  } catch (error) {
1806
- return response_default.error(400 /* BAD_REQUEST */, error.message);
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 };
1807
2416
  }
1808
- const validationErrors = validate(graphqlSchema, parsedQuery);
1809
- if (validationErrors.length) {
1810
- return response_default.error(
1811
- 400 /* BAD_REQUEST */,
1812
- validationErrors.map((err) => err.message).join(", ")
1813
- );
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");
1814
2442
  }
1815
- if (disableIntrospection) {
1816
- const isIntrospectionQuery = parsedQuery.definitions.some(
1817
- (definition) => definition.selectionSet.selections.some(
1818
- (selection) => selection.name.value.startsWith("__")
1819
- )
1820
- );
1821
- if (isIntrospectionQuery) {
1822
- return response_default.error(
1823
- 400 /* BAD_REQUEST */,
1824
- "Introspection is disabled for security reasons."
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("; ")
1825
2451
  );
1826
2452
  }
2453
+ span.addEvent("query-validation-completed", {
2454
+ valid: errors.length === 0,
2455
+ errorCount: errors.length
2456
+ });
1827
2457
  }
1828
- const variables = typeof params.variables === "string" ? JSON.parse(params.variables) : params.variables;
1829
- return response_default.success(
1830
- await graphql({
1831
- schema: graphqlSchema,
1832
- source: query,
1833
- rootValue: graphqlResolvers,
1834
- contextValue: context2,
1835
- variableValues: variables,
1836
- operationName: params.operationName
1837
- })
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
+ )
1838
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;
1839
2497
  }
1840
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);
1841
2550
  }
1842
2551
  };
1843
2552
  __name(_GraphQlAction, "GraphQlAction");
@@ -1902,9 +2611,15 @@ var _OpenwhiskAction = class _OpenwhiskAction {
1902
2611
  * ```typescript
1903
2612
  * const handler = OpenwhiskAction.execute(
1904
2613
  * 'processWebhook',
1905
- * async (params, { logger, headers }) => {
2614
+ * async (params, { logger, headers, telemetry }) => {
1906
2615
  * logger.info({ message: 'Webhook received', event: params.event });
1907
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
+ *
1908
2623
  * // Your webhook processing logic here
1909
2624
  * const result = await processWebhookData(params);
1910
2625
  *
@@ -1919,41 +2634,92 @@ var _OpenwhiskAction = class _OpenwhiskAction {
1919
2634
  static execute(name = "main", action = async (_params) => {
1920
2635
  return { statusCode: 200 /* OK */, body: {} };
1921
2636
  }) {
2637
+ const telemetry = new telemetry_default();
1922
2638
  const openwhiskAction = /* @__PURE__ */ __name(async (params) => {
1923
2639
  params.action_type = "openwhisk-action";
1924
- const logger = telemetry_default.createLogger(name, params);
2640
+ const logger = telemetry.createLogger(name);
1925
2641
  try {
1926
2642
  logger.debug({
1927
- message: `${name}-started`,
1928
- action_name: name
2643
+ message: "OpenWhisk action execution started"
1929
2644
  });
1930
2645
  logger.debug({
1931
- message: `${name}-params`,
2646
+ message: "OpenWhisk action parameters received",
1932
2647
  params
1933
2648
  });
1934
- 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
+ );
1935
2657
  logger.debug({
1936
- message: `${name}-completed`,
2658
+ message: "OpenWhisk action execution completed",
1937
2659
  result
1938
2660
  });
1939
2661
  return result;
1940
2662
  } catch (error) {
1941
2663
  if (error instanceof Error) {
1942
2664
  logger.error({
1943
- message: `${name}-failed`,
2665
+ message: "OpenWhisk action execution failed",
1944
2666
  error: error.message,
1945
2667
  stack: error.stack
1946
2668
  });
1947
2669
  } else {
1948
2670
  logger.error({
1949
- message: `${name}-failed`,
2671
+ message: "OpenWhisk action execution failed",
1950
2672
  error
1951
2673
  });
1952
2674
  }
1953
2675
  return response_default.error(500 /* INTERNAL_ERROR */, "server error");
1954
2676
  }
1955
2677
  }, "openwhiskAction");
1956
- 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);
1957
2723
  }
1958
2724
  };
1959
2725
  __name(_OpenwhiskAction, "OpenwhiskAction");
@@ -2477,41 +3243,17 @@ var _WebhookAction = class _WebhookAction {
2477
3243
  */
2478
3244
  static execute(name = "webhook", requiredParams = [], requiredHeaders = [], signatureVerification = "disabled" /* DISABLED */, action = async () => response_default2.success()) {
2479
3245
  const httpMethods = ["POST" /* POST */];
2480
- const verifySignature = /* @__PURE__ */ __name(async (params) => {
2481
- const signature = params.__ow_headers["x-adobe-commerce-webhook-signature"] || "";
2482
- if (!signature) {
2483
- return "Header `x-adobe-commerce-webhook-signature` not found. Make sure Webhooks signature is enabled in the Commerce instance.";
2484
- }
2485
- const body = params.__ow_body;
2486
- if (!body) {
2487
- return "Request body not found. Make sure the action is configured with `raw-http: true`.";
2488
- }
2489
- let publicKey = params.PUBLIC_KEY;
2490
- if (!publicKey && params.PUBLIC_KEY_BASE64) {
2491
- publicKey = atob(params.PUBLIC_KEY_BASE64);
2492
- }
2493
- if (!publicKey) {
2494
- 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.";
2495
- }
2496
- try {
2497
- const verifier = crypto.createVerify("SHA256");
2498
- verifier.update(body);
2499
- const isSignatureValid = verifier.verify(publicKey, signature, "base64");
2500
- if (!isSignatureValid) {
2501
- return "The signature is invalid.";
2502
- }
2503
- } catch (error) {
2504
- return "The signature is invalid.";
2505
- }
2506
- return null;
2507
- }, "verifySignature");
2508
3246
  const callback = /* @__PURE__ */ __name(async (params, ctx) => {
2509
- const { logger } = ctx;
3247
+ const { logger, telemetry } = ctx;
2510
3248
  if (signatureVerification === "enabled" /* ENABLED */) {
2511
- const verificationErrorMessage = await verifySignature(params);
3249
+ const verificationErrorMessage = await _WebhookAction.verifySignatureWithInstrumentation(
3250
+ name,
3251
+ params,
3252
+ telemetry
3253
+ );
2512
3254
  if (verificationErrorMessage) {
2513
3255
  logger.error({
2514
- message: `${name}-signature-verification-failed`,
3256
+ message: "Webhook action signature verification failed",
2515
3257
  error: verificationErrorMessage
2516
3258
  });
2517
3259
  const verificationErrorResponse = response_default2.exception(verificationErrorMessage);
@@ -2522,21 +3264,198 @@ var _WebhookAction = class _WebhookAction {
2522
3264
  ...JSON.parse(atob(params.__ow_body))
2523
3265
  };
2524
3266
  }
2525
- const errorMessage = validator_default.checkMissingRequestInputs(params, requiredParams, requiredHeaders) ?? "";
3267
+ const errorMessage = _WebhookAction.validateWithInstrumentation(
3268
+ name,
3269
+ params,
3270
+ requiredParams,
3271
+ requiredHeaders,
3272
+ telemetry
3273
+ );
2526
3274
  if (errorMessage) {
2527
3275
  logger.error({
2528
- message: `${name}-validation-failed`,
3276
+ message: "Webhook action validation failed",
2529
3277
  error: errorMessage
2530
3278
  });
2531
3279
  const errorMessageResponse = response_default2.exception(errorMessage);
2532
3280
  return response_default.success(JSON.stringify(errorMessageResponse));
2533
3281
  }
2534
- const response = await action(params, ctx);
3282
+ const response = await _WebhookAction.executeActionWithInstrumentation(
3283
+ name,
3284
+ action,
3285
+ params,
3286
+ ctx
3287
+ );
2535
3288
  return response_default.success(JSON.stringify(response));
2536
3289
  }, "callback");
2537
3290
  runtime_action_default.setActionType("webhook-action");
3291
+ runtime_action_default.setActionTypeName("Webhook action");
2538
3292
  return runtime_action_default.execute(name, httpMethods, [], [], callback);
2539
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
+ }
2540
3459
  };
2541
3460
  __name(_WebhookAction, "WebhookAction");
2542
3461
  var WebhookAction = _WebhookAction;