@nm-logger/logger 1.1.4 → 1.1.5
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 +1 -1
- package/README.md +12 -234
- package/index.d.ts +5 -53
- package/index.js +0 -1
- package/package.json +5 -7
- package/src/DailyWatcher.js +38 -25
- package/src/LogWriter.js +47 -13
- package/src/Logger.js +11 -63
- package/src/Queue.js +1 -4
- package/src/S3Uploader.js +1 -2
- package/src/utils.js +1 -21
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,236 +1,14 @@
|
|
|
1
1
|
# @nm-logger/logger
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
npm install @nm-logger/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("@nm-logger/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
|
|
3
|
+
Express JSON logger with:
|
|
4
|
+
|
|
5
|
+
- Per-request logging (success, error, external API)
|
|
6
|
+
- Separate daily files:
|
|
7
|
+
- `daily_logs_success.json`
|
|
8
|
+
- `daily_logs_error.json`
|
|
9
|
+
- `daily_logs_external.json`
|
|
10
|
+
- Periodic S3 upload (and local file deletion)
|
|
11
|
+
- Correlation IDs (`X-Correlation-ID`)
|
|
12
|
+
- Sensitive field masking
|
|
13
|
+
|
|
14
|
+
See source comments for usage.
|
package/index.d.ts
CHANGED
|
@@ -8,23 +8,9 @@ export interface S3Config {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export interface LoggerOptions {
|
|
11
|
-
/**
|
|
12
|
-
* Local base directory where logs are stored.
|
|
13
|
-
* Default: "logs"
|
|
14
|
-
*/
|
|
15
11
|
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
12
|
maskFields?: string[];
|
|
13
|
+
uploadIntervalMs?: number;
|
|
28
14
|
}
|
|
29
15
|
|
|
30
16
|
export interface LogEntryShape {
|
|
@@ -32,10 +18,12 @@ export interface LogEntryShape {
|
|
|
32
18
|
body: string;
|
|
33
19
|
params: string;
|
|
34
20
|
type: string;
|
|
21
|
+
method: string;
|
|
35
22
|
error: string;
|
|
36
|
-
date: string;
|
|
37
23
|
employee_id: string;
|
|
38
24
|
correlation_id: string;
|
|
25
|
+
date: string;
|
|
26
|
+
category: string;
|
|
39
27
|
}
|
|
40
28
|
|
|
41
29
|
export interface ExternalApiLogOptions {
|
|
@@ -51,32 +39,10 @@ export interface ExternalApiLogOptions {
|
|
|
51
39
|
declare class Logger {
|
|
52
40
|
constructor(s3config?: S3Config, options?: LoggerOptions);
|
|
53
41
|
|
|
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
|
-
*/
|
|
42
|
+
logError(err: any, req?: express.Request, employee_id?: string): Promise<void>;
|
|
67
43
|
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
44
|
logExternalApi(options: ExternalApiLogOptions): Promise<void>;
|
|
74
45
|
|
|
75
|
-
/**
|
|
76
|
-
* Express error-handling middleware.
|
|
77
|
-
* Use: app.use(logger.expressMiddleware());
|
|
78
|
-
* Also ensures X-Correlation-ID header is present.
|
|
79
|
-
*/
|
|
80
46
|
expressMiddleware(): (
|
|
81
47
|
err: any,
|
|
82
48
|
req: express.Request,
|
|
@@ -84,9 +50,6 @@ declare class Logger {
|
|
|
84
50
|
next: express.NextFunction
|
|
85
51
|
) => void;
|
|
86
52
|
|
|
87
|
-
/**
|
|
88
|
-
* Alias for expressMiddleware (for clarity).
|
|
89
|
-
*/
|
|
90
53
|
expressErrorMiddleware(): (
|
|
91
54
|
err: any,
|
|
92
55
|
req: express.Request,
|
|
@@ -94,23 +57,12 @@ declare class Logger {
|
|
|
94
57
|
next: express.NextFunction
|
|
95
58
|
) => void;
|
|
96
59
|
|
|
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
60
|
requestLoggerMiddleware(): (
|
|
103
61
|
req: express.Request,
|
|
104
62
|
res: express.Response,
|
|
105
63
|
next: express.NextFunction
|
|
106
64
|
) => void;
|
|
107
65
|
|
|
108
|
-
/**
|
|
109
|
-
* Attach axios interceptors to log external API calls.
|
|
110
|
-
* Usage:
|
|
111
|
-
* const axios = require("axios");
|
|
112
|
-
* logger.attachAxiosLogger(axios);
|
|
113
|
-
*/
|
|
114
66
|
attachAxiosLogger(axiosInstance: any): void;
|
|
115
67
|
}
|
|
116
68
|
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nm-logger/logger",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.5",
|
|
4
|
+
"description": "Express JSON logger with S3 upload, correlation IDs, and separate success/error/external daily logs.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"scripts": {
|
|
@@ -16,11 +16,9 @@
|
|
|
16
16
|
"json-logger",
|
|
17
17
|
"daily-logs",
|
|
18
18
|
"correlation-id",
|
|
19
|
-
"axios"
|
|
20
|
-
"masking"
|
|
19
|
+
"axios"
|
|
21
20
|
],
|
|
22
|
-
"author": "
|
|
23
|
-
"homepage": "https://virtualemployee.com",
|
|
21
|
+
"author": "nm-logger",
|
|
24
22
|
"license": "MIT",
|
|
25
23
|
"dependencies": {
|
|
26
24
|
"aws-sdk": "^2.1554.0",
|
|
@@ -34,4 +32,4 @@
|
|
|
34
32
|
"@types/node": "^22.0.0",
|
|
35
33
|
"typescript": "^5.6.0"
|
|
36
34
|
}
|
|
37
|
-
}
|
|
35
|
+
}
|
package/src/DailyWatcher.js
CHANGED
|
@@ -1,42 +1,55 @@
|
|
|
1
|
-
const { getDatePath } = require("./utils");
|
|
2
|
-
const path = require("path");
|
|
3
1
|
const fs = require("fs-extra");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { getDatePath } = require("./utils");
|
|
4
|
+
|
|
5
|
+
const CATEGORY_FILE_MAP = {
|
|
6
|
+
success: "daily_logs_success.json",
|
|
7
|
+
error: "daily_logs_error.json",
|
|
8
|
+
external: "daily_logs_external.json"
|
|
9
|
+
};
|
|
4
10
|
|
|
5
11
|
class DailyWatcher {
|
|
6
12
|
constructor(baseDir, queue, s3Uploader, options = {}) {
|
|
7
13
|
this.baseDir = baseDir;
|
|
8
14
|
this.queue = queue;
|
|
9
15
|
this.s3Uploader = s3Uploader;
|
|
16
|
+
this.intervalMs = options.uploadIntervalMs || 60_000; // default 1 minute
|
|
10
17
|
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
console.log(
|
|
19
|
+
"⏱ [@nm-logger/logger] periodic S3 upload every",
|
|
20
|
+
this.intervalMs,
|
|
21
|
+
"ms"
|
|
22
|
+
);
|
|
13
23
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
this.startPeriodicUpload();
|
|
24
|
+
this.start();
|
|
17
25
|
}
|
|
18
26
|
|
|
19
|
-
|
|
27
|
+
start() {
|
|
20
28
|
setInterval(() => {
|
|
21
29
|
const { Y, M, D } = getDatePath();
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
Object.entries(CATEGORY_FILE_MAP).forEach(([category, fileName]) => {
|
|
32
|
+
const file = path.join(
|
|
33
|
+
this.baseDir,
|
|
34
|
+
`${Y}/${M}/${D}`,
|
|
35
|
+
fileName
|
|
36
|
+
);
|
|
37
|
+
const key = `${Y}/${M}/${D}/${fileName}`;
|
|
38
|
+
|
|
39
|
+
if (!fs.existsSync(file)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.queue.add(async () => {
|
|
44
|
+
try {
|
|
45
|
+
console.log(`📤 Uploading [${category}] logs to S3:`, key);
|
|
46
|
+
await this.s3Uploader.upload(file, key);
|
|
47
|
+
console.log("✅ Uploaded. Deleting local file:", file);
|
|
48
|
+
await fs.remove(file);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error(`❌ Error uploading [${category}] logs to S3:`, err);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
40
53
|
});
|
|
41
54
|
}, this.intervalMs);
|
|
42
55
|
}
|
package/src/LogWriter.js
CHANGED
|
@@ -2,27 +2,61 @@ const fs = require("fs-extra");
|
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const { getDatePath, formatDate } = require("./utils");
|
|
4
4
|
|
|
5
|
+
const CATEGORY_FILE_MAP = {
|
|
6
|
+
success: "daily_logs_success.json",
|
|
7
|
+
error: "daily_logs_error.json",
|
|
8
|
+
external: "daily_logs_external.json"
|
|
9
|
+
};
|
|
10
|
+
|
|
5
11
|
class LogWriter {
|
|
6
12
|
constructor(baseDir = "logs") {
|
|
7
13
|
this.baseDir = baseDir;
|
|
8
14
|
}
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const logDir = path.join(this.baseDir, `${Y}/${M}/${D}`);
|
|
17
|
-
const logFile = path.join(logDir, "daily_logs.json");
|
|
16
|
+
async writeLog(log, category = "success") {
|
|
17
|
+
const filePath = this.getFilePath(category);
|
|
18
|
+
|
|
19
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
20
|
+
|
|
21
|
+
let logsWrapper = { logs: [] };
|
|
18
22
|
|
|
19
|
-
await fs.
|
|
23
|
+
if (await fs.pathExists(filePath)) {
|
|
24
|
+
try {
|
|
25
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
26
|
+
if (content.trim()) {
|
|
27
|
+
const parsed = JSON.parse(content);
|
|
28
|
+
if (Array.isArray(parsed.logs)) {
|
|
29
|
+
logsWrapper.logs = parsed.logs;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} catch (e) {
|
|
33
|
+
logsWrapper = { logs: [] };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
20
36
|
|
|
21
|
-
|
|
22
|
-
|
|
37
|
+
const enriched = {
|
|
38
|
+
url: log.url || "",
|
|
39
|
+
body: log.body || "",
|
|
40
|
+
params: log.params || "",
|
|
41
|
+
type: log.type || "",
|
|
42
|
+
method: log.method || "",
|
|
43
|
+
error: log.error || "",
|
|
44
|
+
employee_id: log.employee_id || "",
|
|
45
|
+
correlation_id: log.correlation_id || "",
|
|
46
|
+
date: log.date || formatDate(new Date()),
|
|
47
|
+
category: category || ""
|
|
48
|
+
};
|
|
23
49
|
|
|
24
|
-
|
|
25
|
-
|
|
50
|
+
logsWrapper.logs.push(enriched);
|
|
51
|
+
|
|
52
|
+
await fs.writeFile(filePath, JSON.stringify(logsWrapper, null, 2), "utf8");
|
|
53
|
+
return filePath;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getFilePath(category = "success") {
|
|
57
|
+
const { Y, M, D } = getDatePath();
|
|
58
|
+
const fileName = CATEGORY_FILE_MAP[category] || CATEGORY_FILE_MAP.success;
|
|
59
|
+
return path.join(this.baseDir, `${Y}/${M}/${D}/${fileName}`);
|
|
26
60
|
}
|
|
27
61
|
}
|
|
28
62
|
|
package/src/Logger.js
CHANGED
|
@@ -16,30 +16,19 @@ class Logger {
|
|
|
16
16
|
this.queue = new Queue();
|
|
17
17
|
this.s3Uploader = new S3Uploader(s3config);
|
|
18
18
|
|
|
19
|
-
// Extra fields user wants masked (e.g. ["aadhaar", "pan"])
|
|
20
19
|
this.extraMaskFields = (options.maskFields || []).map((f) =>
|
|
21
20
|
String(f).toLowerCase()
|
|
22
21
|
);
|
|
23
22
|
|
|
24
|
-
// Start daily watcher for S3 uploads
|
|
25
23
|
new DailyWatcher(this.baseDir, this.queue, this.s3Uploader, {
|
|
26
|
-
|
|
24
|
+
uploadIntervalMs: options.uploadIntervalMs
|
|
27
25
|
});
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
/**
|
|
31
|
-
* Mask any sensitive data using default + custom fields.
|
|
32
|
-
*/
|
|
33
28
|
mask(value) {
|
|
34
29
|
return maskSensitive(value, this.extraMaskFields);
|
|
35
30
|
}
|
|
36
31
|
|
|
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
32
|
buildBaseLog(req, employee_id = "") {
|
|
44
33
|
const url = req?.originalUrl || "";
|
|
45
34
|
const bodySrc = req?.body || {};
|
|
@@ -51,18 +40,20 @@ class Logger {
|
|
|
51
40
|
const maskedBody = this.mask(bodySrc);
|
|
52
41
|
const maskedParams = this.mask(paramsObj);
|
|
53
42
|
|
|
54
|
-
// derive correlation ID from request or headers
|
|
55
43
|
let correlationId =
|
|
56
44
|
req?.correlationId ||
|
|
57
45
|
(req?.headers &&
|
|
58
46
|
(req.headers["x-correlation-id"] || req.headers["X-Correlation-ID"])) ||
|
|
59
47
|
"";
|
|
60
48
|
|
|
49
|
+
const method = (req?.method || "").toUpperCase();
|
|
50
|
+
|
|
61
51
|
return {
|
|
62
52
|
url,
|
|
63
53
|
body: JSON.stringify(maskedBody || {}),
|
|
64
54
|
params: JSON.stringify(maskedParams || {}),
|
|
65
55
|
type: getApiType(url),
|
|
56
|
+
method,
|
|
66
57
|
error: "",
|
|
67
58
|
employee_id:
|
|
68
59
|
employee_id ||
|
|
@@ -74,32 +65,20 @@ class Logger {
|
|
|
74
65
|
};
|
|
75
66
|
}
|
|
76
67
|
|
|
77
|
-
/**
|
|
78
|
-
* Log an error (incoming API).
|
|
79
|
-
*/
|
|
80
68
|
async logError(err, req, employee_id = "") {
|
|
81
69
|
const base = this.buildBaseLog(req || {}, employee_id);
|
|
82
|
-
|
|
83
70
|
const logData = {
|
|
84
71
|
...base,
|
|
85
72
|
error: err && err.message ? err.message : String(err || "")
|
|
86
73
|
};
|
|
87
|
-
|
|
88
|
-
await this.logWriter.writeLog(logData);
|
|
74
|
+
await this.logWriter.writeLog(logData, "error");
|
|
89
75
|
}
|
|
90
76
|
|
|
91
|
-
/**
|
|
92
|
-
* Log a normal request (incoming API).
|
|
93
|
-
*/
|
|
94
77
|
async logRequest(req, employee_id = "") {
|
|
95
78
|
const logData = this.buildBaseLog(req, employee_id);
|
|
96
|
-
await this.logWriter.writeLog(logData);
|
|
79
|
+
await this.logWriter.writeLog(logData, "success");
|
|
97
80
|
}
|
|
98
81
|
|
|
99
|
-
/**
|
|
100
|
-
* Log an external API call (axios, etc.).
|
|
101
|
-
* For external APIs, `type` is always "external_api".
|
|
102
|
-
*/
|
|
103
82
|
async logExternalApi({
|
|
104
83
|
url,
|
|
105
84
|
method,
|
|
@@ -117,20 +96,16 @@ class Logger {
|
|
|
117
96
|
body: JSON.stringify(maskedBody || {}),
|
|
118
97
|
params: JSON.stringify(maskedParams || {}),
|
|
119
98
|
type: "external_api",
|
|
99
|
+
method: (method || "").toUpperCase(),
|
|
120
100
|
error: error || "",
|
|
121
|
-
date: undefined, // set by LogWriter
|
|
122
101
|
employee_id: employeeId || "",
|
|
123
|
-
correlation_id: correlationId || ""
|
|
102
|
+
correlation_id: correlationId || "",
|
|
103
|
+
date: undefined
|
|
124
104
|
};
|
|
125
105
|
|
|
126
|
-
await this.logWriter.writeLog(logData);
|
|
106
|
+
await this.logWriter.writeLog(logData, "external");
|
|
127
107
|
}
|
|
128
108
|
|
|
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
109
|
expressMiddleware() {
|
|
135
110
|
return (err, req, res, next) => {
|
|
136
111
|
try {
|
|
@@ -161,22 +136,10 @@ class Logger {
|
|
|
161
136
|
};
|
|
162
137
|
}
|
|
163
138
|
|
|
164
|
-
/**
|
|
165
|
-
* Alias for expressMiddleware (for clarity).
|
|
166
|
-
*/
|
|
167
139
|
expressErrorMiddleware() {
|
|
168
140
|
return this.expressMiddleware();
|
|
169
141
|
}
|
|
170
142
|
|
|
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
143
|
requestLoggerMiddleware() {
|
|
181
144
|
return (req, res, next) => {
|
|
182
145
|
try {
|
|
@@ -203,25 +166,10 @@ class Logger {
|
|
|
203
166
|
};
|
|
204
167
|
}
|
|
205
168
|
|
|
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
169
|
attachAxiosLogger(axiosInstance) {
|
|
222
170
|
if (!axiosInstance || !axiosInstance.interceptors) {
|
|
223
171
|
console.warn(
|
|
224
|
-
"[@
|
|
172
|
+
"[@nm-logger/logger] attachAxiosLogger: provided axios instance is invalid"
|
|
225
173
|
);
|
|
226
174
|
return;
|
|
227
175
|
}
|
package/src/Queue.js
CHANGED
|
@@ -11,7 +11,6 @@ class Queue {
|
|
|
11
11
|
|
|
12
12
|
async run() {
|
|
13
13
|
if (this.processing) return;
|
|
14
|
-
|
|
15
14
|
this.processing = true;
|
|
16
15
|
|
|
17
16
|
while (this.jobs.length) {
|
|
@@ -19,9 +18,7 @@ class Queue {
|
|
|
19
18
|
try {
|
|
20
19
|
await job();
|
|
21
20
|
} catch (err) {
|
|
22
|
-
console.error("Queue job failed
|
|
23
|
-
// Re-add the job for retry (very simple retry mechanism)
|
|
24
|
-
this.jobs.push(job);
|
|
21
|
+
console.error("[@nm-logger/logger] Queue job failed:", err);
|
|
25
22
|
}
|
|
26
23
|
}
|
|
27
24
|
|
package/src/S3Uploader.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const AWS = require("aws-sdk");
|
|
2
1
|
const fs = require("fs");
|
|
2
|
+
const AWS = require("aws-sdk");
|
|
3
3
|
|
|
4
4
|
class S3Uploader {
|
|
5
5
|
constructor(config) {
|
|
@@ -8,7 +8,6 @@ class S3Uploader {
|
|
|
8
8
|
secretAccessKey: config.secretAccessKey,
|
|
9
9
|
region: config.region
|
|
10
10
|
});
|
|
11
|
-
|
|
12
11
|
this.bucket = config.bucket;
|
|
13
12
|
}
|
|
14
13
|
|
package/src/utils.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// Build path segments for logs: YYYY/MM/DD
|
|
2
1
|
exports.getDatePath = () => {
|
|
3
2
|
const now = new Date();
|
|
4
3
|
const Y = now.getFullYear();
|
|
@@ -7,10 +6,8 @@ exports.getDatePath = () => {
|
|
|
7
6
|
return { Y, M, D };
|
|
8
7
|
};
|
|
9
8
|
|
|
10
|
-
// Format date as: 2025-12-05 12:24:00
|
|
11
9
|
exports.formatDate = (d) => {
|
|
12
10
|
const pad = (n) => String(n).padStart(2, "0");
|
|
13
|
-
|
|
14
11
|
return (
|
|
15
12
|
d.getFullYear() +
|
|
16
13
|
"-" +
|
|
@@ -26,15 +23,12 @@ exports.formatDate = (d) => {
|
|
|
26
23
|
);
|
|
27
24
|
};
|
|
28
25
|
|
|
29
|
-
// Extract last segment from URL, used as "type"
|
|
30
|
-
// e.g. "/api/v1/attendance/get" → "get"
|
|
31
26
|
exports.getApiType = (url) => {
|
|
32
27
|
if (!url) return "";
|
|
33
28
|
const segments = url.split("/").filter(Boolean);
|
|
34
29
|
return segments[segments.length - 1] || "";
|
|
35
30
|
};
|
|
36
31
|
|
|
37
|
-
// Default keys to mask (case-insensitive, "includes" match)
|
|
38
32
|
const DEFAULT_MASK_KEYS = [
|
|
39
33
|
"password",
|
|
40
34
|
"pass",
|
|
@@ -49,12 +43,6 @@ const DEFAULT_MASK_KEYS = [
|
|
|
49
43
|
"ssn"
|
|
50
44
|
];
|
|
51
45
|
|
|
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
46
|
exports.maskSensitive = (value, extraFields = []) => {
|
|
59
47
|
const allKeys = [
|
|
60
48
|
...DEFAULT_MASK_KEYS,
|
|
@@ -74,13 +62,7 @@ exports.maskSensitive = (value, extraFields = []) => {
|
|
|
74
62
|
const out = {};
|
|
75
63
|
for (const [k, v] of Object.entries(val)) {
|
|
76
64
|
if (shouldMaskKey(k)) {
|
|
77
|
-
|
|
78
|
-
out[k] = v;
|
|
79
|
-
} else if (typeof v === "string" && v.length) {
|
|
80
|
-
out[k] = "*****";
|
|
81
|
-
} else {
|
|
82
|
-
out[k] = "*****";
|
|
83
|
-
}
|
|
65
|
+
out[k] = "*****";
|
|
84
66
|
} else {
|
|
85
67
|
out[k] = maskAny(v);
|
|
86
68
|
}
|
|
@@ -90,7 +72,6 @@ exports.maskSensitive = (value, extraFields = []) => {
|
|
|
90
72
|
return val;
|
|
91
73
|
};
|
|
92
74
|
|
|
93
|
-
// If it's a string, try JSON.parse and mask
|
|
94
75
|
if (typeof value === "string") {
|
|
95
76
|
try {
|
|
96
77
|
const parsed = JSON.parse(value);
|
|
@@ -103,7 +84,6 @@ exports.maskSensitive = (value, extraFields = []) => {
|
|
|
103
84
|
return maskAny(value);
|
|
104
85
|
};
|
|
105
86
|
|
|
106
|
-
// Simple correlation ID generator
|
|
107
87
|
exports.generateCorrelationId = () => {
|
|
108
88
|
const rand = Math.random().toString(16).slice(2, 10);
|
|
109
89
|
const ts = Date.now().toString(16);
|