@nm-logger/logger 1.2.4 → 1.2.6

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 +1 @@
1
- {"name":"@nm-logger/logger","version":"1.2.4","description":"Express JSON logger with daily success/error/external logs, S3 append uploads, and configurable baseDir.","main":"index.js","types":"index.d.ts","license":"MIT","dependencies":{"@aws-sdk/client-s3":"^3.600.0","fs-extra":"^11.1.1"}}
1
+ {"name":"@nm-logger/logger","version":"1.2.6","description":"Express JSON logger with daily success/error/external logs, S3 append uploads, and configurable baseDir.","main":"index.js","types":"index.d.ts","license":"MIT","dependencies":{"@aws-sdk/client-s3":"^3.600.0","fs-extra":"^11.1.1"}}
package/src/LogWriter.js CHANGED
@@ -6,6 +6,7 @@ const MAP = {
6
6
  success: "daily_logs_success.json",
7
7
  error: "daily_logs_error.json",
8
8
  external: "daily_logs_external.json",
9
+ db_error: "daily_logs_db_error.json",
9
10
  };
10
11
 
11
12
  class LogWriter {
@@ -45,7 +46,7 @@ class LogWriter {
45
46
  type: log.type || "",
46
47
  method: log.method || "",
47
48
  error: log.error || "",
48
- employee_id: log.employee_id || "",
49
+ status_code: log.status_code || "",
49
50
  date: log.date || formatDate(new Date()),
50
51
  });
51
52
 
package/src/Logger.js CHANGED
@@ -51,7 +51,7 @@ class Logger {
51
51
  }
52
52
 
53
53
  // Build base log object for success/error logs
54
- buildBaseLog(req, employee_id = "") {
54
+ buildBaseLog(req, employee_id = "", statusCode) {
55
55
  const url = req && req.originalUrl ? req.originalUrl : "";
56
56
  const body = (req && req.body) || {};
57
57
  const params = {
@@ -79,12 +79,14 @@ class Logger {
79
79
  method,
80
80
  error: "",
81
81
  employee_id: emp,
82
+ status_code:
83
+ typeof statusCode === "number" ? statusCode : statusCode || "",
82
84
  };
83
85
  }
84
86
 
85
87
  // ---- Error logging (Express errors) ----
86
- async logError(err, req, employee_id = "") {
87
- const base = this.buildBaseLog(req || {}, employee_id);
88
+ async logError(err, req, employee_id = "", statusCode) {
89
+ const base = this.buildBaseLog(req || {}, employee_id, statusCode);
88
90
 
89
91
  const log = {
90
92
  ...base,
@@ -95,8 +97,8 @@ class Logger {
95
97
  }
96
98
 
97
99
  // ---- Success logging (normal requests) ----
98
- async logRequest(req, employee_id = "") {
99
- const log = this.buildBaseLog(req, employee_id);
100
+ async logRequest(req, employee_id = "", statusCode) {
101
+ const log = this.buildBaseLog(req, employee_id, statusCode);
100
102
  await this.logWriter.writeLog(log, "success");
101
103
  }
102
104
 
@@ -107,8 +109,8 @@ class Logger {
107
109
  data,
108
110
  params,
109
111
  error,
110
- employeeId,
111
- response, // 👈 NEW: store external API response
112
+ response, // 👈 store external API response
113
+ statusCode,
112
114
  }) {
113
115
  const maskedBody = this.mask(data || {});
114
116
  const maskedParams = this.mask(params || {});
@@ -118,11 +120,11 @@ class Logger {
118
120
  url: url || "",
119
121
  body: JSON.stringify(maskedBody || {}),
120
122
  params: JSON.stringify(maskedParams || {}),
121
- response: JSON.stringify(maskedResponse || {}), // 👈 NEW FIELD
123
+ response: JSON.stringify(maskedResponse || {}),
122
124
  type: "external_api",
123
125
  method: (method || "").toUpperCase(),
124
126
  error: error || "",
125
- employee_id: employeeId || "",
127
+ status_code: typeof statusCode === "number" ? statusCode : (statusCode || ""),
126
128
  };
127
129
 
128
130
  await this.logWriter.writeLog(log, "external");
@@ -132,7 +134,8 @@ class Logger {
132
134
  expressMiddleware() {
133
135
  return (err, req, res, next) => {
134
136
  try {
135
- this.logError(err, req).catch(() => {});
137
+ const statusCode = res && res.statusCode;
138
+ this.logError(err, req, "", statusCode).catch(() => {});
136
139
  } catch (_) {}
137
140
  next(err);
138
141
  };
@@ -144,14 +147,103 @@ class Logger {
144
147
  try {
145
148
  if (!req.__nm_logger_logged) {
146
149
  req.__nm_logger_logged = true;
147
- this.logRequest(req).catch(() => {});
150
+ res.on("finish", () => {
151
+ try {
152
+ const statusCode = res && res.statusCode;
153
+ this.logRequest(req, "", statusCode).catch(() => {});
154
+ } catch (_) {}
155
+ });
148
156
  }
149
157
  } catch (_) {}
150
158
  next();
151
159
  };
152
160
  }
153
161
 
154
- // ---- Axios interceptor ----
162
+
163
+ // ---- Summary helper: filter logs by status_code ----
164
+ /**
165
+ * Get summary of logs filtered by HTTP status code.
166
+ *
167
+ * @param {Object} opt
168
+ * @param {string} [opt.date] - Date string in YYYY-MM-DD format (defaults to today).
169
+ * @param {string} [opt.category] - One of "success", "error", "external", "db_error".
170
+ * @param {number|string} [opt.statusCodePrefix] - If provided, matches codes starting with this (e.g. 5 => 5xx).
171
+ * @param {Array<number|string>} [opt.statusCodes] - If provided, matches any of these exact codes.
172
+ *
173
+ * @returns {Promise<{date:string,category:string,total_logs:number,matched_logs:number,by_status:Object,logs:Array}>}
174
+ */
175
+ async getStatusSummary(opt = {}) {
176
+ const category = opt.category || "error";
177
+ const dateStr = opt.date;
178
+
179
+ // Resolve date parts similar to LogWriter.getFilePath
180
+ const d = dateStr ? new Date(dateStr) : new Date();
181
+ const Y = String(d.getFullYear());
182
+ const M = String(d.getMonth() + 1).padStart(2, "0");
183
+ const D = String(d.getDate()).padStart(2, "0");
184
+
185
+ const MAP = {
186
+ success: "daily_logs_success.json",
187
+ error: "daily_logs_error.json",
188
+ external: "daily_logs_external.json",
189
+ db_error: "daily_logs_db_error.json",
190
+ };
191
+
192
+ const fileName = MAP[category] || MAP.success;
193
+ const filePath = path.join(this.baseDir, `${Y}/${M}/${D}/${fileName}`);
194
+
195
+ const result = {
196
+ date: `${Y}-${M}-${D}`,
197
+ category,
198
+ total_logs: 0,
199
+ matched_logs: 0,
200
+ by_status: {},
201
+ logs: [],
202
+ };
203
+
204
+ if (!(await fs.pathExists(filePath))) {
205
+ return result;
206
+ }
207
+
208
+ try {
209
+ const raw = await fs.readFile(filePath, "utf8");
210
+ if (!raw.trim()) return result;
211
+
212
+ const parsed = JSON.parse(raw);
213
+ const logs = Array.isArray(parsed.logs) ? parsed.logs : [];
214
+ result.total_logs = logs.length;
215
+
216
+ let filtered = logs;
217
+
218
+ if (Array.isArray(opt.statusCodes) && opt.statusCodes.length) {
219
+ const allowed = opt.statusCodes.map((c) => String(c));
220
+ filtered = filtered.filter((log) =>
221
+ allowed.includes(String(log.status_code || ""))
222
+ );
223
+ } else if (opt.statusCodePrefix !== undefined && opt.statusCodePrefix !== null) {
224
+ const prefix = String(opt.statusCodePrefix);
225
+ filtered = filtered.filter((log) =>
226
+ String(log.status_code || "").startsWith(prefix)
227
+ );
228
+ }
229
+
230
+ result.matched_logs = filtered.length;
231
+
232
+ const byStatus = {};
233
+ for (const log of filtered) {
234
+ const code = String(log.status_code || "");
235
+ if (!code) continue;
236
+ byStatus[code] = (byStatus[code] || 0) + 1;
237
+ }
238
+ result.by_status = byStatus;
239
+ result.logs = filtered;
240
+
241
+ return result;
242
+ } catch (_) {
243
+ return result;
244
+ }
245
+ }
246
+ // ---- Axios interceptor ----
155
247
  attachAxiosLogger(axiosInstance) {
156
248
  if (!axiosInstance || !axiosInstance.interceptors) return;
157
249
 
@@ -167,7 +259,7 @@ class Logger {
167
259
  params: cfg.params,
168
260
  response: response.data, // 👈 log external API response body
169
261
  error: null,
170
- employeeId: cfg.employee_id,
262
+ statusCode: response && response.status,
171
263
  }).catch(() => {});
172
264
  } catch (_) {}
173
265
 
@@ -180,6 +272,8 @@ class Logger {
180
272
  const cfg = (error && error.config) || {};
181
273
  const res = error && error.response;
182
274
 
275
+ const statusCode = res && res.status;
276
+
183
277
  this.logExternalApi({
184
278
  url: cfg && cfg.url,
185
279
  method: cfg && cfg.method,
@@ -187,7 +281,7 @@ class Logger {
187
281
  params: cfg && cfg.params,
188
282
  response: res && res.data, // 👈 log response even on error
189
283
  error: error && error.message,
190
- employeeId: cfg && cfg.employee_id,
284
+ statusCode,
191
285
  }).catch(() => {});
192
286
  } catch (_) {}
193
287
 
@@ -195,6 +289,25 @@ class Logger {
195
289
  }
196
290
  );
197
291
  }
292
+
293
+ // ---- Database error logging ----
294
+ async logDbError(error, query = "", params = []) {
295
+ try {
296
+ await this.logWriter.writeLog(
297
+ {
298
+ url: "",
299
+ body: JSON.stringify({ query, params }),
300
+ params: JSON.stringify(params || []),
301
+ response: "",
302
+ type: "db_error",
303
+ method: "",
304
+ error: error && (error.message || String(error)),
305
+ date: new Date().toISOString(),
306
+ },
307
+ "db_error"
308
+ );
309
+ } catch (_) {}
310
+ }
198
311
  }
199
312
 
200
313
  module.exports = Logger;
package/src/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  exports.getDatePath=function(){const d=new Date();return{Y:d.getFullYear(),M:String(d.getMonth()+1).padStart(2,"0"),D:String(d.getDate()).padStart(2,"0")};};
2
2
  exports.formatDate=function(d){const p=n=>String(n).padStart(2,"0");return d.getFullYear()+"-"+p(d.getMonth()+1)+"-"+p(d.getDate())+" "+p(d.getHours())+":"+p(d.getMinutes())+":"+p(d.getSeconds());};
3
3
  exports.getApiType=function(url){if(!url)return"";const s=url.split("/").filter(Boolean);return s[s.length-1]||"";};
4
- const MK=["password","pass","token","secret","otp","auth","authorization","apikey","api_key","session","ssn"];
4
+ const MK=["password","pass","token","secret","otp","auth","authorization","apikey","api_key","session","ssn","AccessKey"];
5
5
  exports.maskSensitive=function(v,extra){const keys=[...MK,...(extra||[])].map(x=>String(x).toLowerCase());const isMask=k=>keys.some(m=>String(k).toLowerCase().includes(m));const maskAny=val=>{if(val&&typeof val==="object"){if(Array.isArray(val))return val.map(maskAny);const o={};for(const[k,x]of Object.entries(val))o[k]=isMask(k)?"*****":maskAny(x);return o;}return val;};if(typeof v==="string"){try{return maskAny(JSON.parse(v));}catch(_){return v;}}return maskAny(v);};
6
6
  exports.streamToString=async s=>await new Promise((res,rej)=>{const c=[];s.on("data",x=>c.push(x));s.on("error",rej);s.on("end",()=>res(Buffer.concat(c).toString("utf8")));});