@nm-logger/logger 1.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Virtual Employee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,236 @@
1
+ # @ve/logger
2
+
3
+ Daily JSON logger for Node.js / Express APIs with:
4
+
5
+ - S3 upload + queue + daily rotation
6
+ - Correlation IDs (with `X-Correlation-ID` header)
7
+ - External API logging (Axios)
8
+ - Sensitive field masking (password, token, otp, etc.)
9
+
10
+ ---
11
+
12
+ ## Installation
13
+
14
+ After you publish the package to npm:
15
+
16
+ ```bash
17
+ npm install @ve/logger
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Log Format
23
+
24
+ Each log line in `daily_logs.json` is a JSON object:
25
+
26
+ ```json
27
+ {
28
+ "url": "/api/v1/attendance/get",
29
+ "body": "{\"month\":\"2025-12\"}",
30
+ "params": "{\"params\":{},\"query\":{}}",
31
+ "type": "get",
32
+ "error": "",
33
+ "date": "2025-12-05 12:24:00",
34
+ "employee_id": "TAKK122",
35
+ "correlation_id": "cid-abcd1234-17f5d3c9a"
36
+ }
37
+ ```
38
+
39
+ Fields:
40
+
41
+ - `url` – `req.originalUrl`
42
+ - `body` – stringified (and masked) `req.body`
43
+ - `params` – stringified (and masked) object `{ params: req.params, query: req.query }`
44
+ - `type` – last segment of the URL (e.g. `/api/v1/attendance/get` → `"get"`)
45
+ - `error` – error message if any
46
+ - `date` – `YYYY-MM-DD HH:mm:ss`
47
+ - `employee_id` – from argument or `req.user.employee_id / emp_code / id`
48
+ - `correlation_id` – unique per request chain (also added as `X-Correlation-ID` header)
49
+
50
+ ---
51
+
52
+ ## Basic Usage
53
+
54
+ ### 1. Create the logger
55
+
56
+ ```js
57
+ const Logger = require("@ve/logger");
58
+
59
+ const logger = new Logger(
60
+ {
61
+ accessKeyId: process.env.AWS_KEY,
62
+ secretAccessKey: process.env.AWS_SECRET,
63
+ region: "ap-south-1",
64
+ bucket: "your-log-bucket-name"
65
+ },
66
+ {
67
+ baseDir: "logs", // optional, default "logs"
68
+ watchIntervalMs: 60000, // optional, default 60s
69
+ maskFields: ["aadhaar", "panNumber"] // extra fields to mask
70
+ }
71
+ );
72
+ ```
73
+
74
+ ### 2. Log every request + set correlation ID header
75
+
76
+ ```js
77
+ app.use(logger.requestLoggerMiddleware());
78
+ ```
79
+
80
+ ### 3. Log errors via Express error middleware
81
+
82
+ ```js
83
+ // your routes above...
84
+
85
+ app.use(logger.expressMiddleware()); // or logger.expressErrorMiddleware()
86
+ ```
87
+
88
+ ### 4. Manual logging in routes
89
+
90
+ ```js
91
+ app.post("/api/v1/attendance/get", async (req, res) => {
92
+ try {
93
+ // ... your logic, external APIs etc ...
94
+
95
+ await logger.logRequest(req, req.user?.employee_id);
96
+ res.json({ success: true });
97
+ } catch (err) {
98
+ await logger.logError(err, req, req.user?.employee_id);
99
+ res.status(500).json({ error: err.message });
100
+ }
101
+ });
102
+ ```
103
+
104
+ ---
105
+
106
+ ## External API logging with Axios
107
+
108
+ ```js
109
+ const axios = require("axios");
110
+
111
+ // Attach once at startup
112
+ logger.attachAxiosLogger(axios);
113
+
114
+ app.get("/api/v1/some-data", async (req, res) => {
115
+ try {
116
+ const response = await axios.get("https://api.example.com/data", {
117
+ headers: {
118
+ "X-Correlation-ID": req.correlationId, // propagated
119
+ "X-Employee-ID": req.user?.employee_id || "" // optional
120
+ },
121
+ params: {
122
+ id: 123
123
+ }
124
+ });
125
+
126
+ res.json(response.data);
127
+ } catch (err) {
128
+ await logger.logError(err, req, req.user?.employee_id);
129
+ res.status(500).json({ error: err.message });
130
+ }
131
+ });
132
+ ```
133
+
134
+ This will produce external log lines like:
135
+
136
+ ```json
137
+ {
138
+ "url": "https://api.example.com/data",
139
+ "body": "{}",
140
+ "params": "{\"id\":123}",
141
+ "type": "external_api",
142
+ "error": "",
143
+ "date": "2025-12-05 12:24:00",
144
+ "employee_id": "TAKK122",
145
+ "correlation_id": "cid-abcd1234-17f5d3c9a"
146
+ }
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Sensitive Data Masking
152
+
153
+ Built-in masked keys (case-insensitive, partial match):
154
+
155
+ - password, pass
156
+ - token, secret
157
+ - otp
158
+ - auth, authorization
159
+ - apiKey, api_key
160
+ - session
161
+ - ssn
162
+
163
+ Plus anything you pass in `maskFields` option.
164
+
165
+ Any object like:
166
+
167
+ ```json
168
+ {
169
+ "password": "MyPass123",
170
+ "otp": "111222",
171
+ "aadhaar": "9999-8888-7777",
172
+ "email": "user@example.com"
173
+ }
174
+ ```
175
+
176
+ will be logged as:
177
+
178
+ ```json
179
+ {
180
+ "password": "*****",
181
+ "otp": "*****",
182
+ "aadhaar": "*****",
183
+ "email": "user@example.com"
184
+ }
185
+ ```
186
+
187
+ ---
188
+
189
+ ## S3 Upload Behavior
190
+
191
+ - Logs are stored locally under:
192
+ - `logs/YYYY/MM/DD/daily_logs.json`
193
+ - A watcher runs every `watchIntervalMs` (default 60 seconds)
194
+ - When the date changes (e.g., from `2025-12-05` to `2025-12-06`),
195
+ - The logger uploads the **previous day's** log file to S3:
196
+
197
+ Example S3 key:
198
+
199
+ ```txt
200
+ 2025/12/05/daily_logs.json
201
+ ```
202
+
203
+ So final S3 path:
204
+
205
+ ```txt
206
+ s3://<bucket>/<year>/<month>/<day>/daily_logs.json
207
+ ```
208
+
209
+ ---
210
+
211
+ ## TypeScript Usage
212
+
213
+ ```ts
214
+ import Logger, { S3Config, LoggerOptions } from "@ve/logger";
215
+
216
+ const s3config: S3Config = {
217
+ accessKeyId: process.env.AWS_KEY!,
218
+ secretAccessKey: process.env.AWS_SECRET!,
219
+ region: "ap-south-1",
220
+ bucket: "your-log-bucket"
221
+ };
222
+
223
+ const options: LoggerOptions = {
224
+ baseDir: "logs",
225
+ watchIntervalMs: 60000,
226
+ maskFields: ["aadhaar", "pan"]
227
+ };
228
+
229
+ const logger = new Logger(s3config, options);
230
+ ```
231
+
232
+ ---
233
+
234
+ ## License
235
+
236
+ MIT
package/index.d.ts ADDED
@@ -0,0 +1,117 @@
1
+ import * as express from "express";
2
+
3
+ export interface S3Config {
4
+ accessKeyId: string;
5
+ secretAccessKey: string;
6
+ region: string;
7
+ bucket: string;
8
+ }
9
+
10
+ export interface LoggerOptions {
11
+ /**
12
+ * Local base directory where logs are stored.
13
+ * Default: "logs"
14
+ */
15
+ baseDir?: string;
16
+
17
+ /**
18
+ * Interval in milliseconds for the daily watcher to check for date change.
19
+ * Default: 60000 (1 minute)
20
+ */
21
+ watchIntervalMs?: number;
22
+
23
+ /**
24
+ * Extra field names to mask in logs (in addition to built-in ones like password, token, otp, etc.).
25
+ * Matching is case-insensitive and uses "includes".
26
+ */
27
+ maskFields?: string[];
28
+ }
29
+
30
+ export interface LogEntryShape {
31
+ url: string;
32
+ body: string;
33
+ params: string;
34
+ type: string;
35
+ error: string;
36
+ date: string;
37
+ employee_id: string;
38
+ correlation_id: string;
39
+ }
40
+
41
+ export interface ExternalApiLogOptions {
42
+ url?: string;
43
+ method?: string;
44
+ data?: any;
45
+ params?: any;
46
+ error?: string | null;
47
+ correlationId?: string;
48
+ employeeId?: string;
49
+ }
50
+
51
+ declare class Logger {
52
+ constructor(s3config?: S3Config, options?: LoggerOptions);
53
+
54
+ /**
55
+ * Log an error for a given request.
56
+ * The log will be appended to the daily JSON file.
57
+ */
58
+ logError(
59
+ err: any,
60
+ req?: express.Request,
61
+ employee_id?: string
62
+ ): Promise<void>;
63
+
64
+ /**
65
+ * Log a normal API request (no error).
66
+ */
67
+ logRequest(req: express.Request, employee_id?: string): Promise<void>;
68
+
69
+ /**
70
+ * Log an external API call (axios, etc.).
71
+ * Type will be "external_api".
72
+ */
73
+ logExternalApi(options: ExternalApiLogOptions): Promise<void>;
74
+
75
+ /**
76
+ * Express error-handling middleware.
77
+ * Use: app.use(logger.expressMiddleware());
78
+ * Also ensures X-Correlation-ID header is present.
79
+ */
80
+ expressMiddleware(): (
81
+ err: any,
82
+ req: express.Request,
83
+ res: express.Response,
84
+ next: express.NextFunction
85
+ ) => void;
86
+
87
+ /**
88
+ * Alias for expressMiddleware (for clarity).
89
+ */
90
+ expressErrorMiddleware(): (
91
+ err: any,
92
+ req: express.Request,
93
+ res: express.Response,
94
+ next: express.NextFunction
95
+ ) => void;
96
+
97
+ /**
98
+ * Express request-logging middleware (non-error).
99
+ * Also generates/propagates correlation ID and sets X-Correlation-ID header.
100
+ * Use near the top of your middleware chain.
101
+ */
102
+ requestLoggerMiddleware(): (
103
+ req: express.Request,
104
+ res: express.Response,
105
+ next: express.NextFunction
106
+ ) => void;
107
+
108
+ /**
109
+ * Attach axios interceptors to log external API calls.
110
+ * Usage:
111
+ * const axios = require("axios");
112
+ * logger.attachAxiosLogger(axios);
113
+ */
114
+ attachAxiosLogger(axiosInstance: any): void;
115
+ }
116
+
117
+ export = Logger;
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ const Logger = require("./src/Logger");
2
+
3
+ module.exports = Logger;
4
+ module.exports.Logger = Logger;
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@nm-logger/logger",
3
+ "version": "1.1.0",
4
+ "description": "Daily JSON logger with S3 upload queue, correlation IDs, external API logging, and sensitive field masking for Express-based APIs.",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "scripts": {
8
+ "test": "node -e \"console.log('no tests yet')\""
9
+ },
10
+ "keywords": [
11
+ "logger",
12
+ "logging",
13
+ "express",
14
+ "s3",
15
+ "aws",
16
+ "json-logger",
17
+ "daily-logs",
18
+ "correlation-id",
19
+ "axios",
20
+ "masking"
21
+ ],
22
+ "author": "Virtual Employee (https://virtualemployee.com)",
23
+ "homepage": "https://virtualemployee.com",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "aws-sdk": "^2.1554.0",
27
+ "fs-extra": "^11.1.1"
28
+ },
29
+ "peerDependencies": {
30
+ "express": ">=4.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/express": "^4.17.21",
34
+ "@types/node": "^22.0.0",
35
+ "typescript": "^5.6.0"
36
+ }
37
+ }
@@ -0,0 +1,53 @@
1
+ const { getDatePath } = require("./utils");
2
+ const path = require("path");
3
+ const fs = require("fs-extra");
4
+
5
+ class DailyWatcher {
6
+ constructor(baseDir, queue, s3Uploader, options = {}) {
7
+ this.baseDir = baseDir;
8
+ this.queue = queue;
9
+ this.s3Uploader = s3Uploader;
10
+ this.intervalMs = options.watchIntervalMs || 60_000; // default: 1 minute
11
+
12
+ this.lastDay = null;
13
+ this.startWatching();
14
+ }
15
+
16
+ startWatching() {
17
+ setInterval(() => {
18
+ const { Y, M, D } = getDatePath();
19
+ const currentDay = `${Y}-${M}-${D}`;
20
+
21
+ if (!this.lastDay) {
22
+ // First run, just set lastDay
23
+ this.lastDay = currentDay;
24
+ return;
25
+ }
26
+
27
+ if (this.lastDay !== currentDay) {
28
+ // Day changed → upload previous day's log
29
+ const lastDayPath = this.lastDay.replace(/-/g, "/");
30
+
31
+ const logFile = path.join(
32
+ this.baseDir,
33
+ lastDayPath,
34
+ "daily_logs.json"
35
+ );
36
+
37
+ if (fs.existsSync(logFile)) {
38
+ const s3Key = `${lastDayPath}/daily_logs.json`; // e.g. 2025/12/05/daily_logs.json
39
+
40
+ this.queue.add(async () => {
41
+ console.log("📤 Uploading previous day logs →", s3Key);
42
+ await this.s3Uploader.upload(logFile, s3Key);
43
+ });
44
+ }
45
+
46
+ // update lastDay to current
47
+ this.lastDay = currentDay;
48
+ }
49
+ }, this.intervalMs);
50
+ }
51
+ }
52
+
53
+ module.exports = DailyWatcher;
@@ -0,0 +1,29 @@
1
+ const fs = require("fs-extra");
2
+ const path = require("path");
3
+ const { getDatePath, formatDate } = require("./utils");
4
+
5
+ class LogWriter {
6
+ constructor(baseDir = "logs") {
7
+ this.baseDir = baseDir;
8
+ }
9
+
10
+ /**
11
+ * Writes a single log entry in the required JSON format
12
+ * and appends it as one line to daily_logs.json
13
+ */
14
+ async writeLog(log) {
15
+ const { Y, M, D } = getDatePath();
16
+ const logDir = path.join(this.baseDir, `${Y}/${M}/${D}`);
17
+ const logFile = path.join(logDir, "daily_logs.json");
18
+
19
+ await fs.ensureDir(logDir);
20
+
21
+ // Ensure date field is always set in required format
22
+ log.date = formatDate(new Date());
23
+
24
+ await fs.appendFile(logFile, JSON.stringify(log) + "\n");
25
+ return logFile;
26
+ }
27
+ }
28
+
29
+ module.exports = LogWriter;
package/src/Logger.js ADDED
@@ -0,0 +1,297 @@
1
+ const LogWriter = require("./LogWriter");
2
+ const Queue = require("./Queue");
3
+ const S3Uploader = require("./S3Uploader");
4
+ const DailyWatcher = require("./DailyWatcher");
5
+ const {
6
+ getApiType,
7
+ maskSensitive,
8
+ generateCorrelationId
9
+ } = require("./utils");
10
+
11
+ class Logger {
12
+ constructor(s3config = {}, options = {}) {
13
+ this.baseDir = options.baseDir || "logs";
14
+
15
+ this.logWriter = new LogWriter(this.baseDir);
16
+ this.queue = new Queue();
17
+ this.s3Uploader = new S3Uploader(s3config);
18
+
19
+ // Extra fields user wants masked (e.g. ["aadhaar", "pan"])
20
+ this.extraMaskFields = (options.maskFields || []).map((f) =>
21
+ String(f).toLowerCase()
22
+ );
23
+
24
+ // Start daily watcher for S3 uploads
25
+ new DailyWatcher(this.baseDir, this.queue, this.s3Uploader, {
26
+ watchIntervalMs: options.watchIntervalMs
27
+ });
28
+ }
29
+
30
+ /**
31
+ * Mask any sensitive data using default + custom fields.
32
+ */
33
+ mask(value) {
34
+ return maskSensitive(value, this.extraMaskFields);
35
+ }
36
+
37
+ /**
38
+ * Build the base log entry with required shape.
39
+ * {
40
+ * url, body, params, type, error, date (added by LogWriter), employee_id, correlation_id
41
+ * }
42
+ */
43
+ buildBaseLog(req, employee_id = "") {
44
+ const url = req?.originalUrl || "";
45
+ const bodySrc = req?.body || {};
46
+ const paramsObj = {
47
+ params: req?.params || {},
48
+ query: req?.query || {}
49
+ };
50
+
51
+ const maskedBody = this.mask(bodySrc);
52
+ const maskedParams = this.mask(paramsObj);
53
+
54
+ // derive correlation ID from request or headers
55
+ let correlationId =
56
+ req?.correlationId ||
57
+ (req?.headers &&
58
+ (req.headers["x-correlation-id"] || req.headers["X-Correlation-ID"])) ||
59
+ "";
60
+
61
+ return {
62
+ url,
63
+ body: JSON.stringify(maskedBody || {}),
64
+ params: JSON.stringify(maskedParams || {}),
65
+ type: getApiType(url),
66
+ error: "",
67
+ employee_id:
68
+ employee_id ||
69
+ (req &&
70
+ req.user &&
71
+ (req.user.employee_id || req.user.emp_code || req.user.id)) ||
72
+ "",
73
+ correlation_id: correlationId
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Log an error (incoming API).
79
+ */
80
+ async logError(err, req, employee_id = "") {
81
+ const base = this.buildBaseLog(req || {}, employee_id);
82
+
83
+ const logData = {
84
+ ...base,
85
+ error: err && err.message ? err.message : String(err || "")
86
+ };
87
+
88
+ await this.logWriter.writeLog(logData);
89
+ }
90
+
91
+ /**
92
+ * Log a normal request (incoming API).
93
+ */
94
+ async logRequest(req, employee_id = "") {
95
+ const logData = this.buildBaseLog(req, employee_id);
96
+ await this.logWriter.writeLog(logData);
97
+ }
98
+
99
+ /**
100
+ * Log an external API call (axios, etc.).
101
+ * For external APIs, `type` is always "external_api".
102
+ */
103
+ async logExternalApi({
104
+ url,
105
+ method,
106
+ data,
107
+ params,
108
+ error,
109
+ correlationId,
110
+ employeeId
111
+ }) {
112
+ const maskedBody = this.mask(data || {});
113
+ const maskedParams = this.mask(params || {});
114
+
115
+ const logData = {
116
+ url: url || "",
117
+ body: JSON.stringify(maskedBody || {}),
118
+ params: JSON.stringify(maskedParams || {}),
119
+ type: "external_api",
120
+ error: error || "",
121
+ date: undefined, // set by LogWriter
122
+ employee_id: employeeId || "",
123
+ correlation_id: correlationId || ""
124
+ };
125
+
126
+ await this.logWriter.writeLog(logData);
127
+ }
128
+
129
+ /**
130
+ * Express error-handling middleware.
131
+ * Use: app.use(logger.expressMiddleware());
132
+ * Also ensures correlation ID is present and added to response header.
133
+ */
134
+ expressMiddleware() {
135
+ return (err, req, res, next) => {
136
+ try {
137
+ let correlationId =
138
+ req.correlationId ||
139
+ (req.headers &&
140
+ (req.headers["x-correlation-id"] ||
141
+ req.headers["X-Correlation-ID"])) ||
142
+ "";
143
+
144
+ if (!correlationId) {
145
+ correlationId = generateCorrelationId();
146
+ req.correlationId = correlationId;
147
+ }
148
+
149
+ if (res && !res.headersSent) {
150
+ res.setHeader("X-Correlation-ID", correlationId);
151
+ }
152
+
153
+ this.logError(err, req).catch((e) =>
154
+ console.error("Logger expressMiddleware error:", e)
155
+ );
156
+ } catch (e) {
157
+ console.error("Logger expressMiddleware outer error:", e);
158
+ }
159
+
160
+ next(err);
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Alias for expressMiddleware (for clarity).
166
+ */
167
+ expressErrorMiddleware() {
168
+ return this.expressMiddleware();
169
+ }
170
+
171
+ /**
172
+ * Express request-logging middleware.
173
+ * - Generates or reuses correlation ID
174
+ * - Sets X-Correlation-ID header on response
175
+ * - Logs each incoming request
176
+ *
177
+ * Use near the top of middleware stack:
178
+ * app.use(logger.requestLoggerMiddleware());
179
+ */
180
+ requestLoggerMiddleware() {
181
+ return (req, res, next) => {
182
+ try {
183
+ let correlationId =
184
+ req.headers["x-correlation-id"] ||
185
+ req.headers["X-Correlation-ID"] ||
186
+ req.correlationId;
187
+
188
+ if (!correlationId) {
189
+ correlationId = generateCorrelationId();
190
+ }
191
+
192
+ req.correlationId = correlationId;
193
+ res.setHeader("X-Correlation-ID", correlationId);
194
+
195
+ this.logRequest(req).catch((e) =>
196
+ console.error("Logger requestLoggerMiddleware error:", e)
197
+ );
198
+ } catch (e) {
199
+ console.error("Logger requestLoggerMiddleware outer error:", e);
200
+ }
201
+
202
+ next();
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Attach axios interceptors to log EXTERNAL API calls.
208
+ *
209
+ * Usage:
210
+ * const axios = require("axios");
211
+ * logger.attachAxiosLogger(axios);
212
+ *
213
+ * In your route:
214
+ * await axios.get("https://api.example.com", {
215
+ * headers: {
216
+ * "X-Correlation-ID": req.correlationId,
217
+ * "X-Employee-ID": req.user?.employee_id
218
+ * }
219
+ * });
220
+ */
221
+ attachAxiosLogger(axiosInstance) {
222
+ if (!axiosInstance || !axiosInstance.interceptors) {
223
+ console.warn(
224
+ "[@ve/logger] attachAxiosLogger: provided axios instance is invalid"
225
+ );
226
+ return;
227
+ }
228
+
229
+ axiosInstance.interceptors.response.use(
230
+ (response) => {
231
+ try {
232
+ const cfg = response.config || {};
233
+ const headers = cfg.headers || {};
234
+
235
+ const correlationId =
236
+ headers["X-Correlation-ID"] ||
237
+ headers["x-correlation-id"] ||
238
+ "";
239
+
240
+ const employeeId =
241
+ headers["X-Employee-ID"] ||
242
+ headers["x-employee-id"] ||
243
+ "";
244
+
245
+ this.logExternalApi({
246
+ url: cfg.url,
247
+ method: cfg.method,
248
+ data: cfg.data,
249
+ params: cfg.params,
250
+ error: null,
251
+ correlationId,
252
+ employeeId
253
+ }).catch((e) =>
254
+ console.error("Logger axios success logExternalApi error:", e)
255
+ );
256
+ } catch (e) {
257
+ console.error("Logger axios response interceptor error:", e);
258
+ }
259
+ return response;
260
+ },
261
+ (error) => {
262
+ try {
263
+ const cfg = error.config || {};
264
+ const headers = cfg ? cfg.headers || {} : {};
265
+
266
+ const correlationId =
267
+ headers["X-Correlation-ID"] ||
268
+ headers["x-correlation-id"] ||
269
+ "";
270
+
271
+ const employeeId =
272
+ headers["X-Employee-ID"] ||
273
+ headers["x-employee-id"] ||
274
+ "";
275
+
276
+ this.logExternalApi({
277
+ url: cfg && cfg.url,
278
+ method: cfg && cfg.method,
279
+ data: cfg && cfg.data,
280
+ params: cfg && cfg.params,
281
+ error: error && error.message,
282
+ correlationId,
283
+ employeeId
284
+ }).catch((e) =>
285
+ console.error("Logger axios error logExternalApi error:", e)
286
+ );
287
+ } catch (e) {
288
+ console.error("Logger axios error interceptor outer error:", e);
289
+ }
290
+
291
+ return Promise.reject(error);
292
+ }
293
+ );
294
+ }
295
+ }
296
+
297
+ module.exports = Logger;
package/src/Queue.js ADDED
@@ -0,0 +1,32 @@
1
+ class Queue {
2
+ constructor() {
3
+ this.jobs = [];
4
+ this.processing = false;
5
+ }
6
+
7
+ add(job) {
8
+ this.jobs.push(job);
9
+ this.run();
10
+ }
11
+
12
+ async run() {
13
+ if (this.processing) return;
14
+
15
+ this.processing = true;
16
+
17
+ while (this.jobs.length) {
18
+ const job = this.jobs.shift();
19
+ try {
20
+ await job();
21
+ } catch (err) {
22
+ console.error("Queue job failed → re-added to queue:", err);
23
+ // Re-add the job for retry (very simple retry mechanism)
24
+ this.jobs.push(job);
25
+ }
26
+ }
27
+
28
+ this.processing = false;
29
+ }
30
+ }
31
+
32
+ module.exports = Queue;
@@ -0,0 +1,28 @@
1
+ const AWS = require("aws-sdk");
2
+ const fs = require("fs");
3
+
4
+ class S3Uploader {
5
+ constructor(config) {
6
+ this.s3 = new AWS.S3({
7
+ accessKeyId: config.accessKeyId,
8
+ secretAccessKey: config.secretAccessKey,
9
+ region: config.region
10
+ });
11
+
12
+ this.bucket = config.bucket;
13
+ }
14
+
15
+ async upload(filePath, s3Key) {
16
+ const fileData = fs.readFileSync(filePath);
17
+
18
+ return this.s3
19
+ .putObject({
20
+ Bucket: this.bucket,
21
+ Key: s3Key,
22
+ Body: fileData
23
+ })
24
+ .promise();
25
+ }
26
+ }
27
+
28
+ module.exports = S3Uploader;
package/src/utils.js ADDED
@@ -0,0 +1,111 @@
1
+ // Build path segments for logs: YYYY/MM/DD
2
+ exports.getDatePath = () => {
3
+ const now = new Date();
4
+ const Y = now.getFullYear();
5
+ const M = String(now.getMonth() + 1).padStart(2, "0");
6
+ const D = String(now.getDate()).padStart(2, "0");
7
+ return { Y, M, D };
8
+ };
9
+
10
+ // Format date as: 2025-12-05 12:24:00
11
+ exports.formatDate = (d) => {
12
+ const pad = (n) => String(n).padStart(2, "0");
13
+
14
+ return (
15
+ d.getFullYear() +
16
+ "-" +
17
+ pad(d.getMonth() + 1) +
18
+ "-" +
19
+ pad(d.getDate()) +
20
+ " " +
21
+ pad(d.getHours()) +
22
+ ":" +
23
+ pad(d.getMinutes()) +
24
+ ":" +
25
+ pad(d.getSeconds())
26
+ );
27
+ };
28
+
29
+ // Extract last segment from URL, used as "type"
30
+ // e.g. "/api/v1/attendance/get" → "get"
31
+ exports.getApiType = (url) => {
32
+ if (!url) return "";
33
+ const segments = url.split("/").filter(Boolean);
34
+ return segments[segments.length - 1] || "";
35
+ };
36
+
37
+ // Default keys to mask (case-insensitive, "includes" match)
38
+ const DEFAULT_MASK_KEYS = [
39
+ "password",
40
+ "pass",
41
+ "token",
42
+ "secret",
43
+ "otp",
44
+ "auth",
45
+ "authorization",
46
+ "apikey",
47
+ "api_key",
48
+ "session",
49
+ "ssn"
50
+ ];
51
+
52
+ /**
53
+ * Deep mask of sensitive fields in any object/array.
54
+ * - Keys containing any of the mask keys are replaced with "*****"
55
+ * - Works recursively
56
+ * - If input is string, tries JSON.parse then masks
57
+ */
58
+ exports.maskSensitive = (value, extraFields = []) => {
59
+ const allKeys = [
60
+ ...DEFAULT_MASK_KEYS,
61
+ ...(extraFields || [])
62
+ ].map((k) => String(k).toLowerCase());
63
+
64
+ const shouldMaskKey = (key) => {
65
+ const lower = String(key).toLowerCase();
66
+ return allKeys.some((mk) => lower.includes(mk));
67
+ };
68
+
69
+ const maskAny = (val) => {
70
+ if (val && typeof val === "object") {
71
+ if (Array.isArray(val)) {
72
+ return val.map(maskAny);
73
+ }
74
+ const out = {};
75
+ for (const [k, v] of Object.entries(val)) {
76
+ if (shouldMaskKey(k)) {
77
+ if (v === null || v === undefined) {
78
+ out[k] = v;
79
+ } else if (typeof v === "string" && v.length) {
80
+ out[k] = "*****";
81
+ } else {
82
+ out[k] = "*****";
83
+ }
84
+ } else {
85
+ out[k] = maskAny(v);
86
+ }
87
+ }
88
+ return out;
89
+ }
90
+ return val;
91
+ };
92
+
93
+ // If it's a string, try JSON.parse and mask
94
+ if (typeof value === "string") {
95
+ try {
96
+ const parsed = JSON.parse(value);
97
+ return maskAny(parsed);
98
+ } catch (_) {
99
+ return value;
100
+ }
101
+ }
102
+
103
+ return maskAny(value);
104
+ };
105
+
106
+ // Simple correlation ID generator
107
+ exports.generateCorrelationId = () => {
108
+ const rand = Math.random().toString(16).slice(2, 10);
109
+ const ts = Date.now().toString(16);
110
+ return `cid-${rand}-${ts}`;
111
+ };