@adobe/spacecat-shared-utils 1.75.0 → 1.76.0
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 +7 -0
- package/README.md +51 -0
- package/package.json +1 -1
- package/src/index.d.ts +30 -0
- package/src/index.js +1 -1
- package/src/log-wrapper.js +30 -14
- package/src/sqs.js +34 -3
- package/src/xray.js +45 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-utils-v1.76.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.75.0...@adobe/spacecat-shared-utils-v1.76.0) (2025-11-20)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **utils,http-utils:** trigger release for trace ID propagation ([#1154](https://github.com/adobe/spacecat-shared/issues/1154)) ([fb48149](https://github.com/adobe/spacecat-shared/commit/fb481497725e49dab18812b4ba1ba7186e35a8f9)), closes [#1152](https://github.com/adobe/spacecat-shared/issues/1152) [#1152](https://github.com/adobe/spacecat-shared/issues/1152) [#1097](https://github.com/adobe/spacecat-shared/issues/1097) [#1097](https://github.com/adobe/spacecat-shared/issues/1097) [#1097](https://github.com/adobe/spacecat-shared/issues/1097) [#1152](https://github.com/adobe/spacecat-shared/issues/1152) [adobe/spacecat-audit-worker#1520](https://github.com/adobe/spacecat-audit-worker/issues/1520)
|
|
7
|
+
|
|
1
8
|
# [@adobe/spacecat-shared-utils-v1.75.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.74.0...@adobe/spacecat-shared-utils-v1.75.0) (2025-11-19)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
This repository contains a collection of shared utility functions used across various SpaceCat projects. These utilities provide a range of checks and validations, from basic data type validation to more complex checks like ISO date strings and URL validation.
|
|
4
4
|
|
|
5
|
+
> **v1.76.0**: Added trace ID propagation support for distributed tracing across SpaceCat services.
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
To install the SpaceCat Shared Utilities, you can use npm:
|
|
@@ -45,6 +47,40 @@ The library includes the following utility functions:
|
|
|
45
47
|
- `hasText(str)`: Checks if the given string is not empty.
|
|
46
48
|
- `dateAfterDays(number)`: Calculates the date after a specified number of days from the current date.
|
|
47
49
|
|
|
50
|
+
## Log Wrapper
|
|
51
|
+
|
|
52
|
+
The `logWrapper` enhances your Lambda function logs by automatically prepending `jobId` (from message) and `traceId` (from AWS X-Ray) to all log statements. This improves log traceability across distributed services.
|
|
53
|
+
|
|
54
|
+
### Features
|
|
55
|
+
- Automatically extracts AWS X-Ray trace ID
|
|
56
|
+
- Includes jobId from message when available
|
|
57
|
+
- Enhances `context.log` directly - **no code changes needed**
|
|
58
|
+
- Works seamlessly with existing log levels (info, error, debug, warn, trace, etc.)
|
|
59
|
+
|
|
60
|
+
### Usage
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
import { logWrapper, sqsEventAdapter } from '@adobe/spacecat-shared-utils';
|
|
64
|
+
|
|
65
|
+
async function run(message, context) {
|
|
66
|
+
const { log } = context;
|
|
67
|
+
|
|
68
|
+
// Use context.log as usual - trace IDs are added automatically
|
|
69
|
+
log.info('Processing started');
|
|
70
|
+
// Output: [jobId=xxx] [traceId=1-xxx-xxx] Processing started
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const main = wrap(run)
|
|
74
|
+
.with(sqsEventAdapter)
|
|
75
|
+
.with(logWrapper) // Add this line early in the wrapper chain
|
|
76
|
+
.with(dataAccess)
|
|
77
|
+
.with(sqs)
|
|
78
|
+
.with(secrets)
|
|
79
|
+
.with(helixStatus);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Note:** The `logWrapper` enhances `context.log` directly. All existing code using `context.log` will automatically include trace IDs and job IDs in logs without any code changes.
|
|
83
|
+
|
|
48
84
|
## SQS Event Adapter
|
|
49
85
|
|
|
50
86
|
The library also includes an SQS event adapter to convert an SQS record into a function parameter. This is useful when working with AWS Lambda functions that are triggered by an SQS event. Usage:
|
|
@@ -62,6 +98,21 @@ export const main = wrap(run)
|
|
|
62
98
|
.with(helixStatus);
|
|
63
99
|
````
|
|
64
100
|
|
|
101
|
+
## AWS X-Ray Integration
|
|
102
|
+
|
|
103
|
+
### getTraceId()
|
|
104
|
+
|
|
105
|
+
Extracts the current AWS X-Ray trace ID from the segment. Returns `null` if not in AWS Lambda or no segment is available.
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
import { getTraceId } from '@adobe/spacecat-shared-utils';
|
|
109
|
+
|
|
110
|
+
const traceId = getTraceId();
|
|
111
|
+
// Returns: '1-5e8e8e8e-5e8e8e8e5e8e8e8e5e8e8e8e' or null
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
This function is automatically used by `logWrapper` to include trace IDs in logs.
|
|
115
|
+
|
|
65
116
|
## Testing
|
|
66
117
|
|
|
67
118
|
This library includes a comprehensive test suite to ensure the reliability of the utility functions. To run the tests, use the following command:
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -66,6 +66,36 @@ export function sqsWrapper(fn: (message: object, context: object) => Promise<Res
|
|
|
66
66
|
export function sqsEventAdapter(fn: (message: object, context: object) => Promise<Response>):
|
|
67
67
|
(request: object, context: object) => Promise<Response>;
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* A higher-order function that wraps a given function and enhances logging by appending
|
|
71
|
+
* a `jobId` and `traceId` to log messages when available.
|
|
72
|
+
* @param fn - The original function to be wrapped
|
|
73
|
+
* @returns A wrapped function that enhances logging
|
|
74
|
+
*/
|
|
75
|
+
export function logWrapper(fn: (message: object, context: object) => Promise<Response>):
|
|
76
|
+
(message: object, context: object) => Promise<Response>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Instruments an AWS SDK v3 client with X-Ray tracing when running in AWS Lambda.
|
|
80
|
+
* @param client - The AWS SDK v3 client to instrument
|
|
81
|
+
* @returns The instrumented client (or original client if not in Lambda)
|
|
82
|
+
*/
|
|
83
|
+
export function instrumentAWSClient<T>(client: T): T;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Extracts the trace ID from the current AWS X-Ray segment.
|
|
87
|
+
* @returns The trace ID if available, or null if not in AWS Lambda or no segment found
|
|
88
|
+
*/
|
|
89
|
+
export function getTraceId(): string | null;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Adds the x-trace-id header to a headers object if a trace ID is available.
|
|
93
|
+
* @param headers - The headers object to augment
|
|
94
|
+
* @param context - The context object that may contain traceId
|
|
95
|
+
* @returns The headers object with x-trace-id added if available
|
|
96
|
+
*/
|
|
97
|
+
export function addTraceIdHeader(headers?: Record<string, string>, context?: object): Record<string, string>;
|
|
98
|
+
|
|
69
99
|
/**
|
|
70
100
|
* Prepends 'https://' schema to the URL if it's not already present.
|
|
71
101
|
* @param url - The URL to modify.
|
package/src/index.js
CHANGED
|
@@ -52,7 +52,7 @@ export { sqsWrapper } from './sqs.js';
|
|
|
52
52
|
export { sqsEventAdapter } from './sqs.js';
|
|
53
53
|
|
|
54
54
|
export { logWrapper } from './log-wrapper.js';
|
|
55
|
-
export { instrumentAWSClient } from './xray.js';
|
|
55
|
+
export { instrumentAWSClient, getTraceId, addTraceIdHeader } from './xray.js';
|
|
56
56
|
|
|
57
57
|
export {
|
|
58
58
|
composeBaseURL,
|
package/src/log-wrapper.js
CHANGED
|
@@ -10,47 +10,63 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { getTraceId } from './xray.js';
|
|
14
|
+
|
|
13
15
|
/**
|
|
14
16
|
* A higher-order function that wraps a given function and enhances logging by appending
|
|
15
|
-
* a `jobId` to log messages when available. This improves traceability of logs
|
|
16
|
-
* with specific jobs or processes.
|
|
17
|
+
* a `jobId` and `traceId` to log messages when available. This improves traceability of logs
|
|
18
|
+
* associated with specific jobs or processes.
|
|
17
19
|
*
|
|
18
20
|
* The wrapper checks if a `log` object exists in the `context` and whether the `message`
|
|
19
|
-
* contains a `jobId`.
|
|
20
|
-
* `
|
|
21
|
-
*
|
|
21
|
+
* contains a `jobId`. It also extracts the AWS X-Ray trace ID if available. If found, log
|
|
22
|
+
* methods (e.g., `info`, `error`, etc.) will prepend the `jobId` and/or `traceId` to all log
|
|
23
|
+
* statements. All existing code using `context.log` will automatically include these markers.
|
|
22
24
|
*
|
|
23
25
|
* @param {function} fn - The original function to be wrapped, called with the provided
|
|
24
26
|
* message and context after logging enhancement.
|
|
25
27
|
* @returns {function(object, object): Promise<Response>} - A wrapped function that enhances
|
|
26
28
|
* logging and returns the result of the original function.
|
|
27
29
|
*
|
|
28
|
-
* `context.
|
|
29
|
-
*
|
|
30
|
+
* `context.log` will be enhanced in place to include `jobId` and/or `traceId` prefixed to all
|
|
31
|
+
* log messages. No code changes needed - existing `context.log` calls work automatically.
|
|
30
32
|
*/
|
|
31
33
|
export function logWrapper(fn) {
|
|
32
34
|
return async (message, context) => {
|
|
33
35
|
const { log } = context;
|
|
34
36
|
|
|
35
37
|
if (log && !context.contextualLog) {
|
|
38
|
+
const markers = [];
|
|
39
|
+
|
|
40
|
+
// Extract jobId from message if available
|
|
36
41
|
if (typeof message === 'object' && message !== null && 'jobId' in message) {
|
|
37
42
|
const { jobId } = message;
|
|
38
|
-
|
|
43
|
+
markers.push(`[jobId=${jobId}]`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Extract traceId from AWS X-Ray
|
|
47
|
+
const traceId = getTraceId();
|
|
48
|
+
if (traceId) {
|
|
49
|
+
markers.push(`[traceId=${traceId}]`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// If we have markers, enhance the log object directly
|
|
53
|
+
if (markers.length > 0) {
|
|
54
|
+
const markerString = markers.join(' ');
|
|
39
55
|
|
|
40
56
|
// Define log levels
|
|
41
57
|
const logLevels = ['info', 'error', 'debug', 'warn', 'trace', 'verbose', 'silly', 'fatal'];
|
|
42
58
|
|
|
43
|
-
// Enhance
|
|
44
|
-
context.
|
|
59
|
+
// Enhance context.log directly to include markers in all log statements
|
|
60
|
+
context.log = logLevels.reduce((accumulator, level) => {
|
|
45
61
|
if (typeof log[level] === 'function') {
|
|
46
|
-
accumulator[level] = (...args) => log[level](
|
|
62
|
+
accumulator[level] = (...args) => log[level](markerString, ...args);
|
|
47
63
|
}
|
|
48
64
|
return accumulator;
|
|
49
65
|
}, {});
|
|
50
|
-
} else {
|
|
51
|
-
log.debug('No jobId found in the provided message. Log entries will be recorded without a jobId.');
|
|
52
|
-
context.contextualLog = log;
|
|
53
66
|
}
|
|
67
|
+
|
|
68
|
+
// Mark that we've processed this context
|
|
69
|
+
context.contextualLog = context.log;
|
|
54
70
|
}
|
|
55
71
|
|
|
56
72
|
return fn(message, context);
|
package/src/sqs.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { Response } from '@adobe/fetch';
|
|
14
14
|
import { SendMessageCommand, SQSClient } from '@aws-sdk/client-sqs';
|
|
15
|
-
import { instrumentAWSClient } from './xray.js';
|
|
15
|
+
import { instrumentAWSClient, getTraceId } from './xray.js';
|
|
16
16
|
|
|
17
17
|
import { hasText, isNonEmptyArray } from './functions.js';
|
|
18
18
|
import { isAWSLambda } from './runtimes.js';
|
|
@@ -46,8 +46,16 @@ class SQS {
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Send a message to an SQS queue. For FIFO queues, messageGroupId is required.
|
|
49
|
+
* Automatically includes traceId in the message payload if available from:
|
|
50
|
+
* 1. The message itself (if explicitly set by caller, e.g. from context.traceId)
|
|
51
|
+
* 2. AWS X-Ray segment (current Lambda execution trace)
|
|
52
|
+
*
|
|
53
|
+
* Special handling for Jobs Dispatcher and similar scenarios:
|
|
54
|
+
* - Set traceId to null to opt-out of trace propagation (each worker gets its own trace)
|
|
55
|
+
*
|
|
49
56
|
* @param {string} queueUrl - The URL of the SQS queue.
|
|
50
57
|
* @param {object} message - The message body to send.
|
|
58
|
+
* Can include traceId for propagation or set to null to opt-out.
|
|
51
59
|
* @param {string} messageGroupId - (Optional) The message group ID for FIFO queues.
|
|
52
60
|
* @return {Promise<void>}
|
|
53
61
|
*/
|
|
@@ -57,6 +65,23 @@ class SQS {
|
|
|
57
65
|
timestamp: new Date().toISOString(),
|
|
58
66
|
};
|
|
59
67
|
|
|
68
|
+
// Handle traceId based on explicit setting or auto-generation
|
|
69
|
+
// Three cases:
|
|
70
|
+
// 1. Property not in message → auto-add X-Ray traceId
|
|
71
|
+
// 2. Property set to null → explicit opt-out (e.g., Jobs Dispatcher)
|
|
72
|
+
// 3. Property has a value → use that value
|
|
73
|
+
if (!('traceId' in message)) {
|
|
74
|
+
// Case 1: No traceId property - auto-add X-Ray trace
|
|
75
|
+
const traceId = getTraceId();
|
|
76
|
+
if (traceId) {
|
|
77
|
+
body.traceId = traceId;
|
|
78
|
+
}
|
|
79
|
+
} else if (message.traceId === null) {
|
|
80
|
+
// Case 2: Explicitly null - opt-out of trace propagation
|
|
81
|
+
delete body.traceId;
|
|
82
|
+
}
|
|
83
|
+
// Case 3: Has a value - already in body from spread, keep it
|
|
84
|
+
|
|
60
85
|
const params = {
|
|
61
86
|
MessageBody: JSON.stringify(body),
|
|
62
87
|
QueueUrl: queueUrl,
|
|
@@ -71,7 +96,7 @@ class SQS {
|
|
|
71
96
|
|
|
72
97
|
try {
|
|
73
98
|
const data = await this.sqsClient.send(msgCommand);
|
|
74
|
-
this.log.debug(`Success, message sent. MessageID:
|
|
99
|
+
this.log.debug(`Success, message sent. MessageID: ${data.MessageId}${body.traceId ? `, TraceID: ${body.traceId}` : ''}`);
|
|
75
100
|
} catch (e) {
|
|
76
101
|
const { type, code, message: msg } = e;
|
|
77
102
|
this.log.error(`Message sent failed. Type: ${type}, Code: ${code}, Message: ${msg}`);
|
|
@@ -95,6 +120,7 @@ export function sqsWrapper(fn) {
|
|
|
95
120
|
/**
|
|
96
121
|
* Wrapper to turn an SQS record into a function param
|
|
97
122
|
* Inspired by https://github.com/adobe/helix-admin/blob/main/src/index.js#L108-L133
|
|
123
|
+
* Extracts traceId from the message payload if present and stores it in context for propagation.
|
|
98
124
|
*
|
|
99
125
|
* @param {UniversalAction} fn
|
|
100
126
|
* @returns {function(object, UniversalContext): Promise<Response>}
|
|
@@ -124,7 +150,12 @@ export function sqsEventAdapter(fn) {
|
|
|
124
150
|
|
|
125
151
|
try {
|
|
126
152
|
message = JSON.parse(record.body);
|
|
127
|
-
log.debug(`Received message with id: ${record.messageId}`);
|
|
153
|
+
log.debug(`Received message with id: ${record.messageId}${message.traceId ? `, traceId: ${message.traceId}` : ''}`);
|
|
154
|
+
|
|
155
|
+
// Store traceId in context if present in the message for downstream propagation
|
|
156
|
+
if (message.traceId) {
|
|
157
|
+
context.traceId = message.traceId;
|
|
158
|
+
}
|
|
128
159
|
} catch (e) {
|
|
129
160
|
log.warn('Function was not invoked properly, message body is not a valid JSON', e);
|
|
130
161
|
return badRequest('Event does not contain a valid message body');
|
package/src/xray.js
CHANGED
|
@@ -16,3 +16,48 @@ import { isAWSLambda } from './runtimes.js';
|
|
|
16
16
|
export function instrumentAWSClient(client) {
|
|
17
17
|
return isAWSLambda() ? AWSXray.captureAWSv3Client(client) : client;
|
|
18
18
|
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extracts the trace ID from the current AWS X-Ray segment.
|
|
22
|
+
* This function is designed to work in AWS Lambda environments where X-Ray tracing is enabled.
|
|
23
|
+
*
|
|
24
|
+
* @returns {string|null} The trace ID if available, or null if not in Lambda or no segment
|
|
25
|
+
*/
|
|
26
|
+
export function getTraceId() {
|
|
27
|
+
if (!isAWSLambda()) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const segment = AWSXray.getSegment();
|
|
32
|
+
if (!segment) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Get the root trace ID
|
|
37
|
+
const effectiveSegment = segment.segment || segment;
|
|
38
|
+
return effectiveSegment.trace_id;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Adds the x-trace-id header to a headers object if a trace ID is available.
|
|
43
|
+
* Checks for traceId from:
|
|
44
|
+
* 1. Explicit context.traceId (from incoming HTTP request or SQS message)
|
|
45
|
+
* 2. AWS X-Ray segment (current Lambda execution)
|
|
46
|
+
*
|
|
47
|
+
* @param {object} headers - The headers object to augment
|
|
48
|
+
* @param {object} context - The context object that may contain traceId
|
|
49
|
+
* @returns {object} The headers object with x-trace-id added if available
|
|
50
|
+
*/
|
|
51
|
+
export function addTraceIdHeader(headers = {}, context = {}) {
|
|
52
|
+
// Priority: 1) context.traceId (propagated from incoming request), 2) X-Ray traceId
|
|
53
|
+
const traceId = context.traceId || getTraceId();
|
|
54
|
+
|
|
55
|
+
if (traceId) {
|
|
56
|
+
return {
|
|
57
|
+
...headers,
|
|
58
|
+
'x-trace-id': traceId,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return headers;
|
|
63
|
+
}
|