@adobe/spacecat-shared-utils 1.82.2 → 1.83.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.83.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.82.3...@adobe/spacecat-shared-utils-v1.83.0) (2025-12-10)
2
+
3
+
4
+ ### Features
5
+
6
+ * Implement Structured (JSON) Logging for Spacecat Audits ([#1232](https://github.com/adobe/spacecat-shared/issues/1232)) ([7eae4d6](https://github.com/adobe/spacecat-shared/commit/7eae4d62fe9f0592f8124082fc66e9754803dd2b))
7
+
8
+ # [@adobe/spacecat-shared-utils-v1.82.3](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.82.2...@adobe/spacecat-shared-utils-v1.82.3) (2025-12-10)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * heading and toc mappers ([#1230](https://github.com/adobe/spacecat-shared/issues/1230)) ([f767fb3](https://github.com/adobe/spacecat-shared/commit/f767fb319c97ed64848a1c5fc5913693a52290d9))
14
+
1
15
  # [@adobe/spacecat-shared-utils-v1.82.2](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.82.1...@adobe/spacecat-shared-utils-v1.82.2) (2025-12-09)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-utils",
3
- "version": "1.82.2",
3
+ "version": "1.83.0",
4
4
  "description": "Shared modules of the Spacecat Services - utils",
5
5
  "type": "module",
6
6
  "exports": {
@@ -41,6 +41,7 @@ export async function detectLocale(config) {
41
41
  const $ = cheerio.load(html);
42
42
 
43
43
  // Execute language detection indicators
44
+ /* c8 ignore start */
44
45
  for (const indicator of indicatorFuncs) {
45
46
  const results = indicator({ baseUrl: parsedBaseUrl, headers, $ });
46
47
  indicatorResults.push(...results);
@@ -56,6 +57,7 @@ export async function detectLocale(config) {
56
57
  }
57
58
  return acc;
58
59
  }, { region: {}, language: {} });
60
+ /* c8 ignore stop */
59
61
  const region = Object.keys(summary.region).length > 0 ? Object.keys(summary.region).sort((a, b) => summary.region[b] - summary.region[a])[0] : 'US';
60
62
  const language = Object.keys(summary.language).length > 0 ? Object.keys(summary.language).sort((a, b) => summary.language[b] - summary.language[a])[0] : 'en';
61
63
 
@@ -13,64 +13,89 @@
13
13
  import { getTraceId } from './xray.js';
14
14
 
15
15
  /**
16
- * A higher-order function that wraps a given function and enhances logging by appending
17
- * a `jobId` and `traceId` to log messages when available. This improves traceability of logs
18
- * associated with specific jobs or processes.
19
- *
20
- * The wrapper checks if a `log` object exists in the `context` and whether the `message`
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.
16
+ * Check if a value is a plain object (not Array, not Error, not null, not other special objects)
17
+ * @param {*} value - The value to check
18
+ * @returns {boolean} - True if the value is a plain object
19
+ */
20
+ function isPlainObject(value) {
21
+ return typeof value === 'object'
22
+ && value !== null
23
+ && !Array.isArray(value)
24
+ && !(value instanceof Error)
25
+ && value.constructor === Object;
26
+ }
27
+
28
+ /**
29
+ * A higher-order function that wraps a given function and enhances logging by converting
30
+ * all logs to JSON format and appending `jobId` and `traceId` to log messages when available.
24
31
  *
25
- * @param {function} fn - The original function to be wrapped, called with the provided
26
- * message and context after logging enhancement.
27
- * @returns {function(object, object): Promise<Response>} - A wrapped function that enhances
28
- * logging and returns the result of the original function.
32
+ * All log messages are automatically converted to structured JSON format:
33
+ * - String messages become: { message: "...", jobId: "...", traceId: "..." }
34
+ * - Object messages are merged with: { ...yourObject, jobId: "...", traceId: "..." }
29
35
  *
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.
36
+ * @param {function} fn - The original function to be wrapped
37
+ * @returns {function(object, object): Promise<Response>} - A wrapped function with JSON logging
32
38
  */
33
39
  export function logWrapper(fn) {
34
40
  return async (message, context) => {
35
41
  const { log } = context;
36
42
 
37
43
  if (log && !context.contextualLog) {
38
- const markers = [];
44
+ const markers = {};
39
45
 
40
46
  // Extract jobId from message if available
41
47
  if (typeof message === 'object' && message !== null && 'jobId' in message) {
42
- const { jobId } = message;
43
- markers.push(`[jobId=${jobId}]`);
48
+ markers.jobId = message.jobId;
44
49
  }
45
50
 
46
51
  // Extract traceId from AWS X-Ray
47
52
  const traceId = getTraceId();
48
53
  if (traceId) {
49
- markers.push(`[traceId=${traceId}]`);
54
+ markers.traceId = traceId;
50
55
  }
51
56
 
52
- // If we have markers, enhance the log object directly
53
- if (markers.length > 0) {
54
- const markerString = markers.join(' ');
57
+ // Define log levels
58
+ const logLevels = ['info', 'error', 'debug', 'warn', 'trace', 'verbose', 'silly', 'fatal'];
55
59
 
56
- // Define log levels
57
- const logLevels = ['info', 'error', 'debug', 'warn', 'trace', 'verbose', 'silly', 'fatal'];
60
+ // Wrap all log methods to output structured JSON
61
+ context.log = logLevels.reduce((accumulator, level) => {
62
+ if (typeof log[level] === 'function') {
63
+ accumulator[level] = (...args) => {
64
+ // If first argument is a plain object, merge with markers
65
+ if (args.length > 0 && isPlainObject(args[0])) {
66
+ return log[level]({ ...markers, ...args[0] });
67
+ }
58
68
 
59
- // Enhance context.log directly to include markers in all log statements
60
- context.log = logLevels.reduce((accumulator, level) => {
61
- if (typeof log[level] === 'function') {
62
- accumulator[level] = (...args) => {
63
- // If first argument is a string (format string), prepend the marker to it
64
- if (args.length > 0 && typeof args[0] === 'string') {
65
- const enhancedArgs = [`${markerString} ${args[0]}`, ...args.slice(1)];
66
- return log[level](...enhancedArgs);
69
+ // If first argument is a string, convert to structured format
70
+ if (args.length > 0 && typeof args[0] === 'string') {
71
+ const logObject = {
72
+ ...markers,
73
+ message: args[0],
74
+ };
75
+
76
+ // If second argument is a plain object, merge it into the log object
77
+ if (args.length > 1 && isPlainObject(args[1])) {
78
+ Object.assign(logObject, args[1]);
79
+
80
+ // If there are more arguments after the object, add them as 'data'
81
+ if (args.length > 2) {
82
+ logObject.data = args.slice(2);
83
+ }
84
+ } else if (args.length > 1) {
85
+ // If there are additional arguments but second is not a plain object,
86
+ // add all additional args as 'data'
87
+ logObject.data = args.slice(1);
67
88
  }
68
- return log[level](...args);
69
- };
70
- }
71
- return accumulator;
72
- }, {});
73
- }
89
+
90
+ return log[level](logObject);
91
+ }
92
+
93
+ // For other types (arrays, primitives, Error objects), wrap in object
94
+ return log[level]({ ...markers, data: args });
95
+ };
96
+ }
97
+ return accumulator;
98
+ }, {});
74
99
 
75
100
  // Mark that we've processed this context
76
101
  context.contextualLog = context.log;