@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 +1 -1
- package/src/LogWriter.js +2 -1
- package/src/Logger.js +127 -14
- package/src/utils.js +1 -1
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@nm-logger/logger","version":"1.2.
|
|
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
|
-
|
|
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
|
-
|
|
111
|
-
|
|
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 || {}),
|
|
123
|
+
response: JSON.stringify(maskedResponse || {}),
|
|
122
124
|
type: "external_api",
|
|
123
125
|
method: (method || "").toUpperCase(),
|
|
124
126
|
error: error || "",
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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")));});
|