sasai-common-utils 1.0.51 → 1.0.53

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sasai-common-utils",
3
- "version": "1.0.51",
3
+ "version": "1.0.53",
4
4
  "description": "Reusable utility library for common logging and other shared features.",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -6,11 +6,20 @@ const { LOG_LEVELS } = require("../logger/constants");
6
6
  * @param {Object} logger - Logger instance.
7
7
  */
8
8
  const logRequest = (request, logger) => {
9
- const { method, url, headers, data, params, query } = request;
10
- const api = "REQUEST"
11
- const logObject = { method, url, headers, requestBody: data, parameters: query, api };
12
- logger.logInfo("Axios Request", logObject);
13
- return request;
9
+ const { method, url, headers, data, params } = request;
10
+ const api = "REQUEST";
11
+
12
+ const logObject = {
13
+ method,
14
+ url,
15
+ headers,
16
+ requestBody: data,
17
+ parameters: params,
18
+ api,
19
+ };
20
+
21
+ logger.logInfo("Axios Request", logObject);
22
+ return request;
14
23
  };
15
24
 
16
25
  /**
@@ -19,12 +28,24 @@ const logRequest = (request, logger) => {
19
28
  * @param {Object} logger - Logger instance.
20
29
  */
21
30
  const logResponse = (response, logger) => {
22
- const { status, config, data } = response;
23
- const { method, url, headers, data: requestBody } = config;
24
- const api = "RESPONSE"
25
- const logObject = { statusCode: status, method, url, responseHeaders:headers, headers, requestBody, responseBody: data, api };
26
- logger.logInfo( "Axios Response", logObject);
27
- return response;
31
+ const { status, config, data, headers: responseHeaders } = response;
32
+ const { method, url, headers, data: requestBody, params } = config || {};
33
+ const api = "RESPONSE";
34
+
35
+ const logObject = {
36
+ statusCode: status,
37
+ method,
38
+ url,
39
+ headers, // request headers
40
+ responseHeaders, // actual response headers
41
+ requestBody,
42
+ responseBody: data,
43
+ parameters: params,
44
+ api,
45
+ };
46
+
47
+ logger.logInfo("Axios Response", logObject);
48
+ return response;
28
49
  };
29
50
 
30
51
  /**
@@ -33,53 +54,55 @@ const logResponse = (response, logger) => {
33
54
  * @param {Object} logger - Logger instance.
34
55
  */
35
56
  const logError = (error, logger) => {
36
- const { response, config } = error;
37
- const { method, url } = config || {};
38
- const logObject = {
39
- method,
40
- url,
41
- status: response?.status,
42
- message: error.message,
43
- stack: error.stack,
44
- };
45
- logger.logError({error}, "Axios Error", logObject);
46
- return Promise.reject(error);
57
+ const { response, config } = error || {};
58
+ const { method, url, headers, data: requestBody, params } = config || {};
59
+
60
+ const logObject = {
61
+ step: "Axios Error",
62
+ error,
63
+ method,
64
+ url,
65
+ headers,
66
+ requestBody,
67
+ parameters: params,
68
+ responseBody: response?.data || {},
69
+ statusCode: response?.status || "",
70
+ responseHeaders: response?.headers || {},
71
+ api: "ERROR",
72
+ };
73
+
74
+ logger.logError(logObject);
75
+ return Promise.reject(error);
47
76
  };
48
77
 
49
78
  /**
50
79
  * Attaches logging interceptors to an Axios instance.
51
80
  * @param {Object} axiosInstance - Axios instance.
52
81
  * @param {Object} logger - Logger instance.
82
+ * @param {Object} traceContext - Trace context provider.
53
83
  */
54
84
  const attachAxiosLogger = (axiosInstance, logger, traceContext) => {
55
- axiosInstance.interceptors.request.use(
56
- (request) =>{
57
- const currentTraceContext = traceContext.getCurrentTraceContext();
58
- request.headers = {
59
- ...request.headers,
60
- "x-b3-traceid": currentTraceContext?.trace_id ?? "",
61
- "x-b3-spanid": currentTraceContext?.span_id ?? "",
62
- "x-b3-parentspanid": currentTraceContext?.parent_span_id ?? ""
63
- };
64
- return logRequest(request, logger)
65
- } ,
66
- (error) => logError(error, logger)
67
- );
85
+ axiosInstance.interceptors.request.use(
86
+ (request) => {
87
+ const currentTraceContext =
88
+ traceContext?.getCurrentTraceContext?.() || {};
89
+
90
+ request.headers = {
91
+ ...request.headers,
92
+ "x-b3-traceid": currentTraceContext?.trace_id ?? "",
93
+ "x-b3-spanid": currentTraceContext?.span_id ?? "",
94
+ "x-b3-parentspanid": currentTraceContext?.parent_span_id ?? "",
95
+ };
68
96
 
69
- axiosInstance.interceptors.response.use(
70
- (response) => {
71
- const currentTraceContext = traceContext.getCurrentTraceContext();
72
- response.headers = {
73
- ...response.headers,
74
- "x-b3-traceid": currentTraceContext?.trace_id ?? "",
75
- "x-b3-spanid": currentTraceContext?.span_id ?? "",
76
- "x-b3-parentspanid": currentTraceContext?.parent_span_id ?? ""
97
+ return logRequest(request, logger);
98
+ },
99
+ (error) => logError(error, logger),
100
+ );
77
101
 
78
- };
79
- return logResponse(response, logger);
80
- } ,
81
- (error) => logError(error, logger)
82
- );
102
+ axiosInstance.interceptors.response.use(
103
+ (response) => logResponse(response, logger),
104
+ (error) => logError(error, logger),
105
+ );
83
106
  };
84
107
 
85
108
  module.exports = { attachAxiosLogger };
@@ -41,6 +41,11 @@ function safeJsonStringify(value) {
41
41
  }
42
42
  }
43
43
 
44
+ function normalizePayload(value) {
45
+ if (typeof value === "string") return cleanString(value);
46
+ return value;
47
+ }
48
+
44
49
  function decodeTokenDetails(headers = {}) {
45
50
  try {
46
51
  const authHeader = headers?.authorization || headers?.Authorization;
@@ -155,11 +160,17 @@ function emitLog(level, data) {
155
160
 
156
161
  const { trace, span, parentSpan, message, ...rest } = data;
157
162
 
163
+ const finalMessage =
164
+ typeof message === "string" && message.trim()
165
+ ? cleanString(message)
166
+ : "Application log";
167
+
158
168
  pinoLogger[level]({
159
169
  trace_id: trace || undefined,
160
170
  span_id: span || undefined,
161
171
  parent_span_id: parentSpan || undefined,
162
- message: cleanString(message),
172
+ body: finalMessage,
173
+ message: finalMessage,
163
174
  ...rest,
164
175
  });
165
176
  }
@@ -181,20 +192,18 @@ function createLogger(config) {
181
192
  parentSpan: traceContext.parent_span_id,
182
193
  thread: "",
183
194
  class: "",
184
- body: safeJsonStringify({
185
- step: data?.step || "",
186
- message,
187
- method: data?.method?.toUpperCase() || "",
188
- parameters: data?.parameters || "",
189
- path: data?.url || "",
190
- headers: data?.headers || {},
191
- responseHeaders: data?.responseHeaders || {},
192
- api: data?.api || "",
193
- tokenDetails,
194
- responseStatus: data?.statusCode || "",
195
- requestBody: data?.requestBody || {},
196
- responseBody: data?.responseBody || {},
197
- }),
195
+ message,
196
+ step: data?.step || "",
197
+ method: data?.method?.toUpperCase() || "",
198
+ parameters: normalizePayload(data?.parameters || ""),
199
+ path: data?.url || "",
200
+ headers: normalizePayload(data?.headers || {}),
201
+ responseHeaders: normalizePayload(data?.responseHeaders || {}),
202
+ api: data?.api || "",
203
+ tokenDetails,
204
+ responseStatus: data?.statusCode || "",
205
+ requestBody: normalizePayload(data?.requestBody || {}),
206
+ responseBody: normalizePayload(data?.responseBody || {}),
198
207
  };
199
208
 
200
209
  const redactedData = redactInformation(logData, {
@@ -208,10 +217,7 @@ function createLogger(config) {
208
217
  const traceContext = getTraceContext();
209
218
  const errorResponse = data?.error?.response;
210
219
  const tokenDetails = decodeTokenDetails(
211
- data?.headers ||
212
- errorResponse?.config?.headers ||
213
- req?.headers ||
214
- {}
220
+ data?.headers || errorResponse?.config?.headers || req?.headers || {},
215
221
  );
216
222
 
217
223
  const logData = {
@@ -220,24 +226,24 @@ function createLogger(config) {
220
226
  parentSpan: traceContext.parent_span_id,
221
227
  thread: "",
222
228
  class: "",
223
- body: safeJsonStringify({
224
- step: data?.step || "",
225
- message: data?.error?.message || "",
226
- method: errorResponse?.config?.method?.toUpperCase() || "",
227
- parameters: data?.parameters || "",
228
- path: errorResponse?.config?.url || req?.originalUrl || "",
229
- responseStatus:
230
- errorResponse?.status || HTTP_STATUS_CODES.BAD_REQUEST,
231
- headers: errorResponse?.config?.headers || req?.headers || {},
232
- requestBody:
233
- errorResponse?.config?.data ||
229
+ message: data?.error?.message || "",
230
+ step: data?.step || "",
231
+ method: errorResponse?.config?.method?.toUpperCase() || "",
232
+ parameters: normalizePayload(data?.parameters || ""),
233
+ path: errorResponse?.config?.url || req?.originalUrl || "",
234
+ responseStatus: errorResponse?.status || HTTP_STATUS_CODES.BAD_REQUEST,
235
+ headers: normalizePayload(
236
+ errorResponse?.config?.headers || req?.headers || {},
237
+ ),
238
+ requestBody: normalizePayload(
239
+ errorResponse?.config?.data ||
234
240
  errorResponse?.config?.body ||
235
241
  req?.body ||
236
242
  {},
237
- responseBody: errorResponse?.data || {},
238
- stack: data?.error?.stack || "",
239
- tokenDetails,
240
- }),
243
+ ),
244
+ responseBody: normalizePayload(errorResponse?.data || {}),
245
+ stack: data?.error?.stack || "",
246
+ tokenDetails,
241
247
  };
242
248
 
243
249
  const redactedData = redactInformation(logData, {
@@ -250,14 +256,20 @@ function createLogger(config) {
250
256
  logDebug: (message, data = {}) => {
251
257
  const traceContext = getTraceContext();
252
258
 
253
- emitLog("debug", {
259
+ const logData = {
254
260
  trace: traceContext.trace_id,
255
261
  span: traceContext.span_id,
256
262
  parentSpan: traceContext.parent_span_id,
257
- body:
263
+ message:
258
264
  typeof message === "string" ? message : safeJsonStringify(message),
259
265
  ...data,
266
+ };
267
+
268
+ const redactedData = redactInformation(logData, {
269
+ sensitiveKeys: globalConfig?.SENSITIVE_KEYS,
260
270
  });
271
+
272
+ emitLog("debug", redactedData);
261
273
  },
262
274
 
263
275
  flush: async () => {
@@ -278,4 +290,4 @@ module.exports = {
278
290
  createLogger,
279
291
  setContextProvider,
280
292
  logger: loggerInstance,
281
- };
293
+ };
@@ -1,61 +1,102 @@
1
1
  /** This file will be used to redact the sensitive informations to be logged */
2
2
 
3
- const traverse = require("traverse");
4
- const { klona } = require("klona/full");
5
-
6
- const defautSensitiveKeys = [/cookie/i, /passw(or)?d/i, /^pw$/, /^pass$/i, /secret/i, /api[-._]?key/i, /token/i, /authorization/i, /otp/i, /^pin$/i ];
7
- let SENSITIVE_KEYS;
8
- function isSensitiveKey(keyStr) {
9
- if (!keyStr) return false;
10
- // Exception for "tokenDetails"
11
- if (/^tokenDetails$/i.test(keyStr)) {
12
- return false; // Do not redact this key
3
+ const traverse = require("traverse");
4
+ const { klona } = require("klona/full");
5
+
6
+ const defaultSensitiveKeys = [
7
+ /cookie/i,
8
+ /passw(or)?d/i,
9
+ /^pw$/i,
10
+ /^pass$/i,
11
+ /secret/i,
12
+ /api[-._]?key/i,
13
+ /token/i,
14
+ /authorization/i,
15
+ /otp/i,
16
+ /^pin$/i,
17
+ ];
18
+
19
+ function buildSensitiveRegexList(sensitiveKeys) {
20
+ try {
21
+ if (!sensitiveKeys) return defaultSensitiveKeys;
22
+
23
+ const parsed =
24
+ typeof sensitiveKeys === "string" ? JSON.parse(sensitiveKeys) : sensitiveKeys;
25
+
26
+ if (!Array.isArray(parsed) || !parsed.length) {
27
+ return defaultSensitiveKeys;
13
28
  }
14
- return SENSITIVE_KEYS.some(regex => regex.test(keyStr));
15
- }
16
29
 
17
- function redactNestedFields(obj) {
18
- Object.entries(obj).forEach(([key, value]) => {
19
- if (isSensitiveKey(key)) {
20
- obj[key] = "[REDACTED]";
21
- } else if (typeof value === "object" && value !== null) {
22
- redactNestedFields(value); // Recursively process nested objects
23
- }
30
+ return parsed.map(key => {
31
+ if (key instanceof RegExp) return key;
32
+
33
+ const parts =
34
+ typeof key === "string" && key.match(/^\/(.*?)\/([gimsuy]*)$/);
35
+
36
+ return parts ? new RegExp(parts[1], parts[2]) : new RegExp(key, "i");
24
37
  });
38
+ } catch (error) {
39
+ return defaultSensitiveKeys;
40
+ }
25
41
  }
26
42
 
27
- function redactObject(obj) {
28
- traverse(obj).forEach(function redactor() {
29
- if (isSensitiveKey(this.key )) {
30
- this.update("[REDACTED]");
31
- }
32
- if (this.key === "message" && typeof this.node === "string") {
33
- let parsedMessage
34
- try {
35
- parsedMessage = JSON.parse(this?.node);
36
- redactNestedFields(parsedMessage);
37
- } catch (error) {
38
- parsedMessage = {};
39
- }
40
-
41
- this.update(JSON.stringify(parsedMessage)); // Update with the redacted string
42
- }
43
- });
43
+ function isSensitiveKey(keyStr, sensitiveRegexList) {
44
+ if (!keyStr) return false;
45
+
46
+ if (/^tokenDetails$/i.test(keyStr)) {
47
+ return false;
48
+ }
49
+
50
+ return sensitiveRegexList.some(regex => regex.test(String(keyStr)));
44
51
  }
45
52
 
46
- const redactInformation=(obj, {sensitiveKeys})=> {
47
- const regexArray = JSON.parse(sensitiveKeys).map((key) => {
48
- const parts = key.match(/^\/(.*?)\/([gimsuy]*)$/);
49
- return parts ? new RegExp(parts[1], parts[2]) : new RegExp(key); // Handle conversion
50
- });
51
- SENSITIVE_KEYS = regexArray || defautSensitiveKeys;
52
- const copy = klona(obj); // Making a deep copy to prevent side effects
53
- redactObject(copy);
53
+ function redactNestedFields(obj, sensitiveRegexList) {
54
+ if (!obj || typeof obj !== "object") return;
54
55
 
55
- const splat = copy[Symbol.for("splat")];
56
- redactObject(splat); // Specifically redact splat Symbol
56
+ Object.entries(obj).forEach(([key, value]) => {
57
+ if (isSensitiveKey(key, sensitiveRegexList)) {
58
+ obj[key] = "[REDACTED]";
59
+ } else if (value && typeof value === "object") {
60
+ redactNestedFields(value, sensitiveRegexList);
61
+ }
62
+ });
63
+ }
64
+
65
+ function redactObject(obj, sensitiveRegexList) {
66
+ if (!obj || typeof obj !== "object") return;
57
67
 
58
- return copy;
68
+ traverse(obj).forEach(function redactor() {
69
+ if (isSensitiveKey(this.key, sensitiveRegexList)) {
70
+ this.update("[REDACTED]");
71
+ return;
72
+ }
73
+
74
+ if (this.key === "message" && typeof this.node === "string") {
75
+ try {
76
+ const parsedMessage = JSON.parse(this.node);
77
+ if (parsedMessage && typeof parsedMessage === "object") {
78
+ redactNestedFields(parsedMessage, sensitiveRegexList);
79
+ this.update(JSON.stringify(parsedMessage));
80
+ }
81
+ } catch (error) {
82
+ // keep original plain-text message unchanged
83
+ }
84
+ }
85
+ });
59
86
  }
60
87
 
61
- module.exports = { redactInformation }
88
+ const redactInformation = (obj, { sensitiveKeys } = {}) => {
89
+ const sensitiveRegexList = buildSensitiveRegexList(sensitiveKeys);
90
+ const copy = klona(obj);
91
+
92
+ redactObject(copy, sensitiveRegexList);
93
+
94
+ const splat = copy?.[Symbol.for("splat")];
95
+ if (splat) {
96
+ redactObject(splat, sensitiveRegexList);
97
+ }
98
+
99
+ return copy;
100
+ };
101
+
102
+ module.exports = { redactInformation };