@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/CHANGELOG.md +116 -0
- package/README.md +28 -9
- package/dist/index.d.mts +46 -16
- package/dist/index.d.ts +46 -16
- package/dist/index.js +1126 -210
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1130 -211
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -5
- package/scripts/postinstall.js +0 -92
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 {
|
|
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
|
|
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
|
|
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 =
|
|
1282
|
-
*
|
|
1283
|
-
*
|
|
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
|
-
|
|
1301
|
-
|
|
1302
|
-
level: params
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
1439
|
+
* await processOrder(orderId);
|
|
1367
1440
|
* } catch (error) {
|
|
1368
|
-
* logger.error(
|
|
1441
|
+
* logger.error({ message: 'Operation failed', ...telemetry.formatError(error) });
|
|
1369
1442
|
* }
|
|
1370
1443
|
* ```
|
|
1371
1444
|
*/
|
|
1372
|
-
|
|
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
|
|
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
|
-
*
|
|
1445
|
-
*
|
|
1446
|
-
*
|
|
1447
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1678
|
+
telemetry.setParams(params);
|
|
1679
|
+
const logger = telemetry.createLogger(name);
|
|
1551
1680
|
try {
|
|
1552
1681
|
logger.debug({
|
|
1553
|
-
message: `${
|
|
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
|
-
|
|
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
|
-
|
|
1570
|
-
|
|
1702
|
+
telemetry,
|
|
1703
|
+
logger
|
|
1571
1704
|
);
|
|
1572
1705
|
if (validationError) {
|
|
1573
1706
|
return validationError;
|
|
1574
1707
|
}
|
|
1575
|
-
const result = await
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
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: `${
|
|
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: `${
|
|
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
|
|
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
|
|
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: `${
|
|
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: `${
|
|
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 =
|
|
1976
|
+
const logger = telemetry.createLogger(name);
|
|
1724
1977
|
try {
|
|
1725
1978
|
logger.debug({
|
|
1726
|
-
message:
|
|
1727
|
-
action_name: name
|
|
1979
|
+
message: "Event consumer action execution started"
|
|
1728
1980
|
});
|
|
1729
1981
|
logger.debug({
|
|
1730
|
-
message:
|
|
1982
|
+
message: "Event consumer action headers received",
|
|
1731
1983
|
headers: params.__ow_headers || {}
|
|
1732
1984
|
});
|
|
1733
1985
|
logger.debug({
|
|
1734
|
-
message:
|
|
1986
|
+
message: "Event consumer action parameters received",
|
|
1735
1987
|
parameters: params
|
|
1736
1988
|
});
|
|
1737
|
-
const errorMessage =
|
|
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:
|
|
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
|
|
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:
|
|
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({
|
|
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
|
|
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
|
-
|
|
1788
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
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
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
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
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
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
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
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 =
|
|
2640
|
+
const logger = telemetry.createLogger(name);
|
|
1925
2641
|
try {
|
|
1926
2642
|
logger.debug({
|
|
1927
|
-
message:
|
|
1928
|
-
action_name: name
|
|
2643
|
+
message: "OpenWhisk action execution started"
|
|
1929
2644
|
});
|
|
1930
2645
|
logger.debug({
|
|
1931
|
-
message:
|
|
2646
|
+
message: "OpenWhisk action parameters received",
|
|
1932
2647
|
params
|
|
1933
2648
|
});
|
|
1934
|
-
const result = await
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
3249
|
+
const verificationErrorMessage = await _WebhookAction.verifySignatureWithInstrumentation(
|
|
3250
|
+
name,
|
|
3251
|
+
params,
|
|
3252
|
+
telemetry
|
|
3253
|
+
);
|
|
2512
3254
|
if (verificationErrorMessage) {
|
|
2513
3255
|
logger.error({
|
|
2514
|
-
message:
|
|
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 =
|
|
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:
|
|
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
|
|
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;
|