@adobe/spacecat-shared-utils 1.75.0 → 1.77.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 CHANGED
@@ -1,3 +1,17 @@
1
+ # [@adobe/spacecat-shared-utils-v1.77.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.76.0...@adobe/spacecat-shared-utils-v1.77.0) (2025-11-20)
2
+
3
+
4
+ ### Features
5
+
6
+ * utils function getTemporalCondition extended ([#1155](https://github.com/adobe/spacecat-shared/issues/1155)) ([aad2d1c](https://github.com/adobe/spacecat-shared/commit/aad2d1c7229e98c7e45f07a541c81c1d4cf7c0c4))
7
+
8
+ # [@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)
9
+
10
+
11
+ ### Features
12
+
13
+ * **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)
14
+
1
15
  # [@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
16
 
3
17
 
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-utils",
3
- "version": "1.75.0",
3
+ "version": "1.77.0",
4
4
  "description": "Shared modules of the Spacecat Services - utils",
5
5
  "type": "module",
6
6
  "exports": {
@@ -202,10 +202,43 @@ export function getMonthInfo(inputMonth = null, inputYear = null) {
202
202
  }
203
203
 
204
204
  // --- Public: Main decision function ---
205
- export function getTemporalCondition({ week, month, year } = {}) {
205
+ export function getTemporalCondition({
206
+ week,
207
+ month,
208
+ year,
209
+ numSeries = 1,
210
+ } = {}) {
206
211
  const hasWeek = Number.isInteger(week) && Number.isInteger(year);
207
212
  const hasMonth = Number.isInteger(month) && Number.isInteger(year);
208
213
 
214
+ if (numSeries > 1) {
215
+ if (!hasWeek && !hasMonth) {
216
+ throw new Error('Missing required parameters: week or month');
217
+ }
218
+
219
+ const conditions = [];
220
+ for (let i = 0; i < numSeries; i += 1) {
221
+ if (hasWeek) {
222
+ let currentWeek = week - i;
223
+ let currentYear = year;
224
+ if (currentWeek < 1) {
225
+ currentWeek = has53CalendarWeeks(currentYear) ? 53 : 52;
226
+ currentYear -= 1;
227
+ }
228
+ conditions.push(getWeekInfo(currentWeek, currentYear).temporalCondition);
229
+ } else if (hasMonth) {
230
+ let currentMonth = month - i;
231
+ let currentYear = year;
232
+ if (currentMonth < 1) {
233
+ currentMonth = 12;
234
+ currentYear -= 1;
235
+ }
236
+ conditions.push(getMonthInfo(currentMonth, currentYear).temporalCondition);
237
+ }
238
+ }
239
+ return conditions.join(' OR ');
240
+ }
241
+
209
242
  if (hasWeek && isValidWeek(week, year)) {
210
243
  return getWeekInfo(week, year).temporalCondition;
211
244
  }
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,
@@ -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 associated
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`. If found, log methods (e.g., `info`, `error`, etc.) will prepend the
20
- * `jobId` to all log statements where `context.contextualLog` is used. If no `jobId` is found,
21
- * logging will remain unchanged.
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.contextualLog` will include logging methods with `jobId` prefixed, or fall back
29
- * to the existing `log` object if no `jobId` is provided.
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
- const jobIdMarker = `[jobId=${jobId}]`;
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 the log object to include jobId in all log statements
44
- context.contextualLog = logLevels.reduce((accumulator, level) => {
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](jobIdMarker, ...args);
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: ${data.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
+ }