db-backup-logging 1.0.1 → 1.0.3
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/README.md +66 -12
- package/dist/index.d.mts +38 -2
- package/dist/index.d.ts +38 -2
- package/dist/index.js +114 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +119 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -6
package/README.md
CHANGED
|
@@ -21,6 +21,11 @@ npm install mongoose express
|
|
|
21
21
|
npm install @aws-sdk/client-s3
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
**Optional** (only if using automatic backup/cleanup schedulers):
|
|
25
|
+
```bash
|
|
26
|
+
npm install node-cron
|
|
27
|
+
```
|
|
28
|
+
|
|
24
29
|
---
|
|
25
30
|
|
|
26
31
|
## Quick Start
|
|
@@ -102,11 +107,52 @@ logCustomError(new Error('Payment failed'), {
|
|
|
102
107
|
})
|
|
103
108
|
```
|
|
104
109
|
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
### 6. Automatic Backup Scheduler
|
|
113
|
+
|
|
114
|
+
> Requires `node-cron` installed in your project.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { initBackupScheduler } from 'db-backup-logging'
|
|
118
|
+
|
|
119
|
+
// Call after initializeNoticePackage()
|
|
120
|
+
initBackupScheduler()
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Schedule is resolved in priority order:
|
|
124
|
+
1. `BACKUP_CRON_SCHEDULE` / `BACKUP_TIMEZONE` env vars
|
|
125
|
+
2. `config.backupCron` passed to `initializeNoticePackage()`
|
|
126
|
+
3. Default: `"0 2 * * *"` (2 AM daily, UTC)
|
|
127
|
+
|
|
128
|
+
### 7. Automatic Log Cleanup Scheduler
|
|
129
|
+
|
|
130
|
+
> Requires `node-cron` installed in your project.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { initLogCleanupScheduler } from 'db-backup-logging'
|
|
134
|
+
|
|
135
|
+
// Call after initializeNoticePackage()
|
|
136
|
+
initLogCleanupScheduler()
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Schedule and retention are resolved in priority order:
|
|
140
|
+
1. `LOG_CLEANUP_CRON_SCHEDULE` / `LOG_CLEANUP_TIMEZONE` / `LOG_RETENTION_DAYS` env vars
|
|
141
|
+
2. `config.logCleanupCron` passed to `initializeNoticePackage()`
|
|
142
|
+
3. Default: `"0 3 * * *"` (3 AM daily, UTC), 30 days retention
|
|
143
|
+
|
|
144
|
+
|
|
105
145
|
---
|
|
106
146
|
|
|
107
147
|
## Full Config Reference
|
|
108
148
|
|
|
109
149
|
```ts
|
|
150
|
+
interface CronConfig {
|
|
151
|
+
schedule: string // Cron expression, e.g. "0 2 * * *"
|
|
152
|
+
timezone?: string // IANA timezone, e.g. "Asia/Kolkata"
|
|
153
|
+
retentionDays?: number // Days to keep (logCleanupCron only)
|
|
154
|
+
}
|
|
155
|
+
|
|
110
156
|
interface NoticeConfig {
|
|
111
157
|
dbType: 'mongodb' | 'postgres' | 'mysql'
|
|
112
158
|
dbUri: string // MongoDB connection URI
|
|
@@ -118,20 +164,26 @@ interface NoticeConfig {
|
|
|
118
164
|
errorLogs: string // Collection name for error logs
|
|
119
165
|
backupLogs: string // Collection name for backup logs
|
|
120
166
|
}
|
|
121
|
-
aws?: {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
accessKeyId: string
|
|
126
|
-
secretAccessKey: string
|
|
127
|
-
}
|
|
128
|
-
local?: {
|
|
129
|
-
enabled: boolean
|
|
130
|
-
backupPath: string // Relative or absolute path
|
|
131
|
-
}
|
|
167
|
+
aws?: { ... } // S3 config (optional)
|
|
168
|
+
local?: { ... } // Local backup config (optional)
|
|
169
|
+
backupCron?: CronConfig // Auto backup schedule (optional)
|
|
170
|
+
logCleanupCron?: CronConfig // Log cleanup schedule (optional)
|
|
132
171
|
}
|
|
133
172
|
```
|
|
134
173
|
|
|
174
|
+
### Environment Variables
|
|
175
|
+
|
|
176
|
+
| Variable | Description | Default |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `MONGO_URI` | MongoDB connection URI | — |
|
|
179
|
+
| `BACKUP_PATH` | Local backup directory | — |
|
|
180
|
+
| `BACKUP_CRON_SCHEDULE` | Backup cron expression | `0 2 * * *` |
|
|
181
|
+
| `BACKUP_TIMEZONE` | Backup cron timezone | `UTC` |
|
|
182
|
+
| `LOG_CLEANUP_CRON_SCHEDULE` | Log cleanup cron expression | `0 3 * * *` |
|
|
183
|
+
| `LOG_CLEANUP_TIMEZONE` | Log cleanup timezone | `UTC` |
|
|
184
|
+
| `LOG_RETENTION_DAYS` | Days to retain logs | `30` |
|
|
185
|
+
|
|
186
|
+
|
|
135
187
|
---
|
|
136
188
|
|
|
137
189
|
## Exported Modules
|
|
@@ -143,8 +195,10 @@ interface NoticeConfig {
|
|
|
143
195
|
| `errorMiddleware()` | Middleware | Express error-handling middleware |
|
|
144
196
|
| `logCustomError(error, context?)` | Function | Manually log any error |
|
|
145
197
|
| `manualBackupTrigger()` | Async Function | Trigger a database backup on demand |
|
|
198
|
+
| `initBackupScheduler()` | Function | Start automatic backup cron job *(requires node-cron)* |
|
|
199
|
+
| `initLogCleanupScheduler()` | Function | Start automatic log cleanup cron job *(requires node-cron)* |
|
|
146
200
|
|
|
147
|
-
All types are also exported: `NoticeConfig`, `AWSConfig`, `LocalConfig`, `NoticeTables`, `RequestLogEntry`, `ErrorLogEntry`, `BackupLogEntry`.
|
|
201
|
+
All types are also exported: `NoticeConfig`, `AWSConfig`, `LocalConfig`, `NoticeTables`, `CronConfig`, `RequestLogEntry`, `ErrorLogEntry`, `BackupLogEntry`.
|
|
148
202
|
|
|
149
203
|
---
|
|
150
204
|
## Notes
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { Request, Response, NextFunction } from 'express';
|
|
2
2
|
|
|
3
|
+
interface CronConfig {
|
|
4
|
+
/** Cron expression, e.g. "0 2 * * *" for 2 AM daily */
|
|
5
|
+
schedule: string;
|
|
6
|
+
/** IANA timezone string, e.g. "Asia/Kolkata" */
|
|
7
|
+
timezone?: string;
|
|
8
|
+
/** How many days to retain items before cleanup (applies to logCleanupCron) */
|
|
9
|
+
retentionDays?: number;
|
|
10
|
+
}
|
|
3
11
|
interface AWSConfig {
|
|
4
12
|
enabled: boolean;
|
|
5
13
|
bucketName: string;
|
|
@@ -25,6 +33,10 @@ interface NoticeConfig {
|
|
|
25
33
|
environment?: string;
|
|
26
34
|
aws?: AWSConfig;
|
|
27
35
|
local?: LocalConfig;
|
|
36
|
+
/** Cron config for automatic database backups (requires node-cron peer dep) */
|
|
37
|
+
backupCron?: CronConfig;
|
|
38
|
+
/** Cron config for automatic log cleanup (requires node-cron peer dep) */
|
|
39
|
+
logCleanupCron?: CronConfig;
|
|
28
40
|
}
|
|
29
41
|
interface RequestLogEntry {
|
|
30
42
|
method: string;
|
|
@@ -65,7 +77,7 @@ interface BackupLogEntry {
|
|
|
65
77
|
}
|
|
66
78
|
|
|
67
79
|
/**
|
|
68
|
-
* Initialize the
|
|
80
|
+
* Initialize the db-backup-logging package.
|
|
69
81
|
* Must be called once during application startup, after mongoose.connect().
|
|
70
82
|
*
|
|
71
83
|
* @param config - Full package configuration
|
|
@@ -102,4 +114,28 @@ declare function logCustomError(error: Error | string, context?: {
|
|
|
102
114
|
};
|
|
103
115
|
}): void;
|
|
104
116
|
|
|
105
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Start an automatic backup cron job.
|
|
119
|
+
*
|
|
120
|
+
* Schedule and timezone are resolved in order:
|
|
121
|
+
* 1. Environment variables BACKUP_CRON_SCHEDULE / BACKUP_TIMEZONE
|
|
122
|
+
* 2. config.backupCron passed to initializeNoticePackage()
|
|
123
|
+
* 3. Built-in defaults ("0 2 * * *" = 2 AM daily, UTC)
|
|
124
|
+
*
|
|
125
|
+
* Requires node-cron >= 3.0.0 installed in the host project.
|
|
126
|
+
*/
|
|
127
|
+
declare function initBackupScheduler(): void;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Start an automatic log cleanup cron job.
|
|
131
|
+
*
|
|
132
|
+
* Schedule, timezone and retention days are resolved in order:
|
|
133
|
+
* 1. Environment variables LOG_CLEANUP_CRON_SCHEDULE / LOG_CLEANUP_TIMEZONE / LOG_RETENTION_DAYS
|
|
134
|
+
* 2. config.logCleanupCron passed to initializeNoticePackage()
|
|
135
|
+
* 3. Built-in defaults ("0 3 * * *" = 3 AM daily, UTC, 30 days retention)
|
|
136
|
+
*
|
|
137
|
+
* Requires node-cron >= 3.0.0 installed in the host project.
|
|
138
|
+
*/
|
|
139
|
+
declare function initLogCleanupScheduler(): void;
|
|
140
|
+
|
|
141
|
+
export { type AWSConfig, type BackupLogEntry, type CronConfig, type ErrorLogEntry, type LocalConfig, type NoticeConfig, type NoticeTables, type RequestLogEntry, errorMiddleware, initBackupScheduler, initLogCleanupScheduler, initializeNoticePackage, logCustomError, manualBackupTrigger, requestLogger };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { Request, Response, NextFunction } from 'express';
|
|
2
2
|
|
|
3
|
+
interface CronConfig {
|
|
4
|
+
/** Cron expression, e.g. "0 2 * * *" for 2 AM daily */
|
|
5
|
+
schedule: string;
|
|
6
|
+
/** IANA timezone string, e.g. "Asia/Kolkata" */
|
|
7
|
+
timezone?: string;
|
|
8
|
+
/** How many days to retain items before cleanup (applies to logCleanupCron) */
|
|
9
|
+
retentionDays?: number;
|
|
10
|
+
}
|
|
3
11
|
interface AWSConfig {
|
|
4
12
|
enabled: boolean;
|
|
5
13
|
bucketName: string;
|
|
@@ -25,6 +33,10 @@ interface NoticeConfig {
|
|
|
25
33
|
environment?: string;
|
|
26
34
|
aws?: AWSConfig;
|
|
27
35
|
local?: LocalConfig;
|
|
36
|
+
/** Cron config for automatic database backups (requires node-cron peer dep) */
|
|
37
|
+
backupCron?: CronConfig;
|
|
38
|
+
/** Cron config for automatic log cleanup (requires node-cron peer dep) */
|
|
39
|
+
logCleanupCron?: CronConfig;
|
|
28
40
|
}
|
|
29
41
|
interface RequestLogEntry {
|
|
30
42
|
method: string;
|
|
@@ -65,7 +77,7 @@ interface BackupLogEntry {
|
|
|
65
77
|
}
|
|
66
78
|
|
|
67
79
|
/**
|
|
68
|
-
* Initialize the
|
|
80
|
+
* Initialize the db-backup-logging package.
|
|
69
81
|
* Must be called once during application startup, after mongoose.connect().
|
|
70
82
|
*
|
|
71
83
|
* @param config - Full package configuration
|
|
@@ -102,4 +114,28 @@ declare function logCustomError(error: Error | string, context?: {
|
|
|
102
114
|
};
|
|
103
115
|
}): void;
|
|
104
116
|
|
|
105
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Start an automatic backup cron job.
|
|
119
|
+
*
|
|
120
|
+
* Schedule and timezone are resolved in order:
|
|
121
|
+
* 1. Environment variables BACKUP_CRON_SCHEDULE / BACKUP_TIMEZONE
|
|
122
|
+
* 2. config.backupCron passed to initializeNoticePackage()
|
|
123
|
+
* 3. Built-in defaults ("0 2 * * *" = 2 AM daily, UTC)
|
|
124
|
+
*
|
|
125
|
+
* Requires node-cron >= 3.0.0 installed in the host project.
|
|
126
|
+
*/
|
|
127
|
+
declare function initBackupScheduler(): void;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Start an automatic log cleanup cron job.
|
|
131
|
+
*
|
|
132
|
+
* Schedule, timezone and retention days are resolved in order:
|
|
133
|
+
* 1. Environment variables LOG_CLEANUP_CRON_SCHEDULE / LOG_CLEANUP_TIMEZONE / LOG_RETENTION_DAYS
|
|
134
|
+
* 2. config.logCleanupCron passed to initializeNoticePackage()
|
|
135
|
+
* 3. Built-in defaults ("0 3 * * *" = 3 AM daily, UTC, 30 days retention)
|
|
136
|
+
*
|
|
137
|
+
* Requires node-cron >= 3.0.0 installed in the host project.
|
|
138
|
+
*/
|
|
139
|
+
declare function initLogCleanupScheduler(): void;
|
|
140
|
+
|
|
141
|
+
export { type AWSConfig, type BackupLogEntry, type CronConfig, type ErrorLogEntry, type LocalConfig, type NoticeConfig, type NoticeTables, type RequestLogEntry, errorMiddleware, initBackupScheduler, initLogCleanupScheduler, initializeNoticePackage, logCustomError, manualBackupTrigger, requestLogger };
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
errorMiddleware: () => errorMiddleware,
|
|
34
|
+
initBackupScheduler: () => initBackupScheduler,
|
|
35
|
+
initLogCleanupScheduler: () => initLogCleanupScheduler,
|
|
34
36
|
initializeNoticePackage: () => initializeNoticePackage,
|
|
35
37
|
logCustomError: () => logCustomError,
|
|
36
38
|
manualBackupTrigger: () => manualBackupTrigger,
|
|
@@ -46,7 +48,7 @@ function setConfig(config) {
|
|
|
46
48
|
function getConfig() {
|
|
47
49
|
if (!_config) {
|
|
48
50
|
throw new Error(
|
|
49
|
-
"[
|
|
51
|
+
"[db-backup-logging] Package not initialized. Call initializeNoticePackage() first."
|
|
50
52
|
);
|
|
51
53
|
}
|
|
52
54
|
return _config;
|
|
@@ -132,7 +134,7 @@ async function flush() {
|
|
|
132
134
|
retries++;
|
|
133
135
|
if (retries >= MAX_RETRIES) {
|
|
134
136
|
console.error(
|
|
135
|
-
`[
|
|
137
|
+
`[db-backup-logging] Failed to write ${docs.length} logs to "${collectionName}" after ${MAX_RETRIES} retries:`,
|
|
136
138
|
err instanceof Error ? err.message : err
|
|
137
139
|
);
|
|
138
140
|
} else {
|
|
@@ -209,42 +211,49 @@ function logCustomError(error, context) {
|
|
|
209
211
|
// src/init.ts
|
|
210
212
|
function initializeNoticePackage(config) {
|
|
211
213
|
if (!config.dbType) {
|
|
212
|
-
throw new Error('[
|
|
214
|
+
throw new Error('[db-backup-logging] "dbType" is required in config.');
|
|
213
215
|
}
|
|
214
216
|
if (!config.dbUri) {
|
|
215
|
-
throw new Error('[
|
|
217
|
+
throw new Error('[db-backup-logging] "dbUri" is required in config.');
|
|
216
218
|
}
|
|
217
219
|
if (!config.tables) {
|
|
218
|
-
throw new Error('[
|
|
220
|
+
throw new Error('[db-backup-logging] "tables" configuration is required.');
|
|
219
221
|
}
|
|
220
222
|
if (!config.tables.requestLogs || !config.tables.errorLogs || !config.tables.backupLogs) {
|
|
221
223
|
throw new Error(
|
|
222
|
-
"[
|
|
224
|
+
"[db-backup-logging] All table names (requestLogs, errorLogs, backupLogs) must be configured."
|
|
223
225
|
);
|
|
224
226
|
}
|
|
225
227
|
if (config.aws?.enabled) {
|
|
226
228
|
if (!config.aws.bucketName || !config.aws.region) {
|
|
227
229
|
throw new Error(
|
|
228
|
-
'[
|
|
230
|
+
'[db-backup-logging] AWS "bucketName" and "region" are required when AWS is enabled.'
|
|
229
231
|
);
|
|
230
232
|
}
|
|
231
233
|
if (!config.aws.accessKeyId || !config.aws.secretAccessKey) {
|
|
232
234
|
throw new Error(
|
|
233
|
-
'[
|
|
235
|
+
'[db-backup-logging] AWS "accessKeyId" and "secretAccessKey" are required when AWS is enabled.'
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
require.resolve("@aws-sdk/client-s3");
|
|
240
|
+
} catch {
|
|
241
|
+
throw new Error(
|
|
242
|
+
'[db-backup-logging] AWS backup is enabled in config, but "@aws-sdk/client-s3" is not installed. Please run: npm install @aws-sdk/client-s3'
|
|
234
243
|
);
|
|
235
244
|
}
|
|
236
245
|
}
|
|
237
246
|
if (config.local?.enabled) {
|
|
238
247
|
if (!config.local.backupPath) {
|
|
239
248
|
throw new Error(
|
|
240
|
-
'[
|
|
249
|
+
'[db-backup-logging] Local "backupPath" is required when local backup is enabled.'
|
|
241
250
|
);
|
|
242
251
|
}
|
|
243
252
|
}
|
|
244
253
|
setConfig(config);
|
|
245
254
|
registerExceptionHandlers();
|
|
246
255
|
console.log(
|
|
247
|
-
`[
|
|
256
|
+
`[db-backup-logging] Initialized successfully (db: ${config.dbType}, service: ${config.serviceName || "default"}, env: ${config.environment || process.env.NODE_ENV || "development"})`
|
|
248
257
|
);
|
|
249
258
|
}
|
|
250
259
|
|
|
@@ -307,7 +316,7 @@ async function performBackup() {
|
|
|
307
316
|
const dumpDir = path.join(tempDir, fileName);
|
|
308
317
|
const archivePath = path.join(tempDir, archiveName);
|
|
309
318
|
try {
|
|
310
|
-
console.log(`[
|
|
319
|
+
console.log(`[db-backup-logging] Starting backup: ${archiveName}`);
|
|
311
320
|
await execAsync(`mongodump --uri="${dbUri}" --out="${dumpDir}"`);
|
|
312
321
|
await execAsync(`zip -r "${archivePath}" "${fileName}"`, { cwd: tempDir });
|
|
313
322
|
const stats = fs.statSync(archivePath);
|
|
@@ -319,7 +328,7 @@ async function performBackup() {
|
|
|
319
328
|
}
|
|
320
329
|
const localPath = path.join(localDir, archiveName);
|
|
321
330
|
fs.copyFileSync(archivePath, localPath);
|
|
322
|
-
console.log(`[
|
|
331
|
+
console.log(`[db-backup-logging] Backup saved locally: ${localPath}`);
|
|
323
332
|
await logBackup({
|
|
324
333
|
backupFileName: archiveName,
|
|
325
334
|
backupType: "full",
|
|
@@ -337,9 +346,9 @@ async function performBackup() {
|
|
|
337
346
|
if (fs.existsSync(dumpDir)) {
|
|
338
347
|
fs.rmSync(dumpDir, { recursive: true, force: true });
|
|
339
348
|
}
|
|
340
|
-
console.log(`[
|
|
349
|
+
console.log(`[db-backup-logging] Backup completed: ${archiveName}`);
|
|
341
350
|
} catch (err) {
|
|
342
|
-
console.error("[
|
|
351
|
+
console.error("[db-backup-logging] Backup failed:", err instanceof Error ? err.message : err);
|
|
343
352
|
await logBackup({
|
|
344
353
|
backupFileName: archiveName,
|
|
345
354
|
backupType: "full",
|
|
@@ -370,7 +379,7 @@ async function uploadToS3(filePath, fileName, fileSize) {
|
|
|
370
379
|
ContentType: "application/zip"
|
|
371
380
|
});
|
|
372
381
|
await s3Client.send(command);
|
|
373
|
-
console.log(`[
|
|
382
|
+
console.log(`[db-backup-logging] Backup uploaded to S3: backups/${fileName}`);
|
|
374
383
|
await logBackup({
|
|
375
384
|
backupFileName: fileName,
|
|
376
385
|
backupType: "full",
|
|
@@ -389,7 +398,7 @@ async function logBackup(entry) {
|
|
|
389
398
|
});
|
|
390
399
|
} catch (err) {
|
|
391
400
|
console.error(
|
|
392
|
-
"[
|
|
401
|
+
"[db-backup-logging] Failed to write backup log:",
|
|
393
402
|
err instanceof Error ? err.message : err
|
|
394
403
|
);
|
|
395
404
|
}
|
|
@@ -397,9 +406,98 @@ async function logBackup(entry) {
|
|
|
397
406
|
async function manualBackupTrigger() {
|
|
398
407
|
await performBackup();
|
|
399
408
|
}
|
|
409
|
+
|
|
410
|
+
// src/schedulers/backup.ts
|
|
411
|
+
function loadNodeCron() {
|
|
412
|
+
try {
|
|
413
|
+
return require("node-cron");
|
|
414
|
+
} catch {
|
|
415
|
+
throw new Error(
|
|
416
|
+
"[db-backup-logging] node-cron is required to use initBackupScheduler().\nPlease install it in your root project:\n npm install node-cron\n yarn add node-cron"
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function initBackupScheduler() {
|
|
421
|
+
const cron = loadNodeCron();
|
|
422
|
+
const config = getConfig();
|
|
423
|
+
const schedule = process.env.BACKUP_CRON_SCHEDULE || config.backupCron?.schedule || "0 2 * * *";
|
|
424
|
+
const timezone = process.env.BACKUP_TIMEZONE || config.backupCron?.timezone || "UTC";
|
|
425
|
+
if (!cron.validate(schedule)) {
|
|
426
|
+
throw new Error(
|
|
427
|
+
`[db-backup-logging] Invalid BACKUP_CRON_SCHEDULE: "${schedule}". Please provide a valid cron expression.`
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
cron.schedule(
|
|
431
|
+
schedule,
|
|
432
|
+
async () => {
|
|
433
|
+
console.log("[db-backup-logging] Backup scheduler triggered.");
|
|
434
|
+
await manualBackupTrigger();
|
|
435
|
+
},
|
|
436
|
+
{ timezone }
|
|
437
|
+
);
|
|
438
|
+
console.log(
|
|
439
|
+
`[db-backup-logging] Backup scheduler started \u2014 schedule: "${schedule}", timezone: "${timezone}"`
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/schedulers/cleanup.ts
|
|
444
|
+
function loadNodeCron2() {
|
|
445
|
+
try {
|
|
446
|
+
return require("node-cron");
|
|
447
|
+
} catch {
|
|
448
|
+
throw new Error(
|
|
449
|
+
"[db-backup-logging] node-cron is required to use initLogCleanupScheduler().\nPlease install it in your root project:\n npm install node-cron\n yarn add node-cron"
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
async function purgeOldLogs(retentionDays) {
|
|
454
|
+
const config = getConfig();
|
|
455
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
456
|
+
cutoff.setDate(cutoff.getDate() - retentionDays);
|
|
457
|
+
const collections = [config.tables.requestLogs, config.tables.errorLogs];
|
|
458
|
+
for (const collectionName of collections) {
|
|
459
|
+
try {
|
|
460
|
+
const Model2 = getModel(collectionName);
|
|
461
|
+
const result = await Model2.deleteMany({ createdAt: { $lt: cutoff } });
|
|
462
|
+
console.log(
|
|
463
|
+
`[db-backup-logging] Log cleanup: removed ${result.deletedCount} record(s) from "${collectionName}" older than ${retentionDays} day(s).`
|
|
464
|
+
);
|
|
465
|
+
} catch (err) {
|
|
466
|
+
console.error(
|
|
467
|
+
`[db-backup-logging] Log cleanup failed for "${collectionName}":`,
|
|
468
|
+
err instanceof Error ? err.message : err
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
function initLogCleanupScheduler() {
|
|
474
|
+
const cron = loadNodeCron2();
|
|
475
|
+
const config = getConfig();
|
|
476
|
+
const schedule = process.env.LOG_CLEANUP_CRON_SCHEDULE || config.logCleanupCron?.schedule || "0 3 * * *";
|
|
477
|
+
const timezone = process.env.LOG_CLEANUP_TIMEZONE || config.logCleanupCron?.timezone || "UTC";
|
|
478
|
+
const retentionDays = Number(process.env.LOG_RETENTION_DAYS) || config.logCleanupCron?.retentionDays || 30;
|
|
479
|
+
if (!cron.validate(schedule)) {
|
|
480
|
+
throw new Error(
|
|
481
|
+
`[db-backup-logging] Invalid LOG_CLEANUP_CRON_SCHEDULE: "${schedule}". Please provide a valid cron expression.`
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
cron.schedule(
|
|
485
|
+
schedule,
|
|
486
|
+
async () => {
|
|
487
|
+
console.log("[db-backup-logging] Log cleanup scheduler triggered.");
|
|
488
|
+
await purgeOldLogs(retentionDays);
|
|
489
|
+
},
|
|
490
|
+
{ timezone }
|
|
491
|
+
);
|
|
492
|
+
console.log(
|
|
493
|
+
`[db-backup-logging] Log cleanup scheduler started \u2014 schedule: "${schedule}", timezone: "${timezone}", retentionDays: ${retentionDays}`
|
|
494
|
+
);
|
|
495
|
+
}
|
|
400
496
|
// Annotate the CommonJS export names for ESM import in node:
|
|
401
497
|
0 && (module.exports = {
|
|
402
498
|
errorMiddleware,
|
|
499
|
+
initBackupScheduler,
|
|
500
|
+
initLogCleanupScheduler,
|
|
403
501
|
initializeNoticePackage,
|
|
404
502
|
logCustomError,
|
|
405
503
|
manualBackupTrigger,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/utils/config.ts","../src/utils/db.ts","../src/utils/queue.ts","../src/exceptions/index.ts","../src/init.ts","../src/logger/index.ts","../src/backup/index.ts"],"sourcesContent":["// ─── Public API ─────────────────────────────────────────────────────────────\n\nexport { initializeNoticePackage } from './init'\nexport { requestLogger } from './logger'\nexport { manualBackupTrigger } from './backup'\nexport { logCustomError, errorMiddleware } from './exceptions'\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport type {\n NoticeConfig,\n AWSConfig,\n LocalConfig,\n NoticeTables,\n RequestLogEntry,\n ErrorLogEntry,\n BackupLogEntry,\n} from './types'\n","import type { NoticeConfig } from '../types'\n\nlet _config: NoticeConfig | null = null\n\nexport function setConfig(config: NoticeConfig): void {\n _config = { ...config }\n}\n\nexport function getConfig(): NoticeConfig {\n if (!_config) {\n throw new Error(\n '[notice-utility] Package not initialized. Call initializeNoticePackage() first.'\n )\n }\n return _config\n}\n","import mongoose, { Schema, Model, Connection } from 'mongoose'\nimport { getConfig } from './config'\n\nconst modelCache = new Map<string, Model<any>>()\n\nlet localConnection: Connection | null = null\n\nexport function getDbConnection(): Connection {\n if (!localConnection) {\n localConnection = mongoose.createConnection(getConfig().dbUri)\n }\n return localConnection\n}\n\n/**\n * Gets or creates a Mongoose model for the given collection name.\n * Uses a flexible mixed schema to support any log structure.\n */\nexport function getModel(collectionName: string): Model<any> {\n if (modelCache.has(collectionName)) {\n return modelCache.get(collectionName)!\n }\n\n const schema = new Schema(\n {},\n {\n strict: false,\n timestamps: false,\n collection: collectionName,\n }\n )\n\n // Use a dedicated connection for notice-utility to avoid 'npm link' duplication issues\n const conn: Connection = getDbConnection()\n\n // Avoid OverwriteModelError — check if model already exists\n let model: Model<any>\n try {\n model = conn.model(collectionName)\n } catch {\n model = conn.model(collectionName, schema)\n }\n\n modelCache.set(collectionName, model)\n return model\n}\n\n/**\n * Creates a separate Mongoose connection for backup purposes (mongodump URI).\n */\nexport function getDbUri(): string {\n return getConfig().dbUri\n}\n","import { getModel } from './db'\n\ninterface QueueItem {\n collectionName: string\n data: Record<string, unknown>\n}\n\nconst queue: QueueItem[] = []\nlet isProcessing = false\nconst BATCH_SIZE = 50\nconst FLUSH_INTERVAL_MS = 5000\nconst MAX_RETRIES = 3\n\nlet flushTimer: ReturnType<typeof setInterval> | null = null\n\n/**\n * Enqueue a log entry for async writing to the database.\n * This never throws — all errors are silently logged to console.\n */\nexport function enqueue(collectionName: string, data: Record<string, unknown>): void {\n queue.push({ collectionName, data })\n\n // Start the flush interval if not already running\n if (!flushTimer) {\n flushTimer = setInterval(() => {\n flush().catch(() => {})\n }, FLUSH_INTERVAL_MS)\n\n // Unref so the timer doesn't keep the process alive\n if (flushTimer && typeof flushTimer === 'object' && 'unref' in flushTimer) {\n flushTimer.unref()\n }\n }\n\n // If queue reaches batch size, flush immediately\n if (queue.length >= BATCH_SIZE) {\n flush().catch(() => {})\n }\n}\n\n/**\n * Flush all queued log entries to the database.\n */\nexport async function flush(): Promise<void> {\n if (isProcessing || queue.length === 0) return\n isProcessing = true\n\n const batch = queue.splice(0, BATCH_SIZE)\n\n // Group by collection\n const grouped = new Map<string, Record<string, unknown>[]>()\n for (const item of batch) {\n const list = grouped.get(item.collectionName) || []\n list.push(item.data)\n grouped.set(item.collectionName, list)\n }\n\n for (const [collectionName, docs] of grouped) {\n let retries = 0\n while (retries < MAX_RETRIES) {\n try {\n const Model = getModel(collectionName)\n await Model.insertMany(docs, { ordered: false })\n break\n } catch (err) {\n retries++\n if (retries >= MAX_RETRIES) {\n console.error(\n `[notice-utility] Failed to write ${docs.length} logs to \"${collectionName}\" after ${MAX_RETRIES} retries:`,\n err instanceof Error ? err.message : err\n )\n } else {\n // Brief delay before retry\n await new Promise((r) => setTimeout(r, 100 * retries))\n }\n }\n }\n }\n\n isProcessing = false\n\n // If there are more items in the queue, continue flushing\n if (queue.length > 0) {\n flush().catch(() => {})\n }\n}\n","import type { Request, Response, NextFunction } from 'express'\nimport { getConfig } from '../utils/config'\nimport { enqueue } from '../utils/queue'\n\n/**\n * Log an error entry to the configured errorLogs collection.\n */\nfunction logError(\n error: Error | string,\n context?: {\n req?: Request\n userId?: string\n }\n): void {\n try {\n const config = getConfig()\n const err = typeof error === 'string' ? new Error(error) : error\n\n const logEntry: Record<string, unknown> = {\n errorMessage: err.message,\n stackTrace: err.stack || null,\n requestContext: context?.req\n ? {\n method: context.req.method,\n url: context.req.originalUrl || context.req.url,\n headers: context.req.headers,\n body: context.req.body || null,\n }\n : null,\n userId:\n context?.userId ||\n (context?.req as any)?.user?.id ||\n (context?.req as any)?.user?._id ||\n null,\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.errorLogs, logEntry)\n } catch {\n // Silent — error logging failure must never cause further errors\n }\n}\n\n/**\n * Register global process-level exception handlers.\n * Chains with existing handlers — does NOT override them.\n */\nexport function registerExceptionHandlers(): void {\n // Store references to any existing handlers so we can chain\n const existingUncaught = process.listeners('uncaughtException').slice()\n const existingRejection = process.listeners('unhandledRejection').slice()\n\n // uncaughtException\n process.on('uncaughtException', (error: Error) => {\n logError(error)\n // Don't prevent the default crash behavior for truly unrecoverable errors\n // The existing handlers will still fire since we're adding, not replacing\n })\n\n // unhandledRejection\n process.on('unhandledRejection', (reason: unknown) => {\n const error = reason instanceof Error ? reason : new Error(String(reason))\n logError(error)\n })\n}\n\n/**\n * Express error-handling middleware.\n * Must be registered AFTER all routes: app.use(errorMiddleware())\n */\nexport function errorMiddleware() {\n return (err: Error, req: Request, res: Response, next: NextFunction): void => {\n logError(err, { req })\n\n // Pass to the next error handler (don't swallow the error)\n next(err)\n }\n}\n\n/**\n * Public export — manually log a custom error with optional context.\n */\nexport function logCustomError(\n error: Error | string,\n context?: {\n userId?: string\n requestContext?: {\n method?: string\n url?: string\n headers?: Record<string, string | string[] | undefined>\n body?: unknown\n }\n }\n): void {\n try {\n const config = getConfig()\n const err = typeof error === 'string' ? new Error(error) : error\n\n const logEntry: Record<string, unknown> = {\n errorMessage: err.message,\n stackTrace: err.stack || null,\n requestContext: context?.requestContext || null,\n userId: context?.userId || null,\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.errorLogs, logEntry)\n } catch {\n // Silent\n }\n}\n","import type { NoticeConfig } from './types'\nimport { setConfig } from './utils/config'\nimport { registerExceptionHandlers } from './exceptions'\n\n/**\n * Initialize the notice-utility package.\n * Must be called once during application startup, after mongoose.connect().\n *\n * @param config - Full package configuration\n */\nexport function initializeNoticePackage(config: NoticeConfig): void {\n // Validate required fields\n if (!config.dbType) {\n throw new Error('[notice-utility] \"dbType\" is required in config.')\n }\n if (!config.dbUri) {\n throw new Error('[notice-utility] \"dbUri\" is required in config.')\n }\n if (!config.tables) {\n throw new Error('[notice-utility] \"tables\" configuration is required.')\n }\n if (!config.tables.requestLogs || !config.tables.errorLogs || !config.tables.backupLogs) {\n throw new Error(\n '[notice-utility] All table names (requestLogs, errorLogs, backupLogs) must be configured.'\n )\n }\n\n // Validate AWS config if enabled\n if (config.aws?.enabled) {\n if (!config.aws.bucketName || !config.aws.region) {\n throw new Error(\n '[notice-utility] AWS \"bucketName\" and \"region\" are required when AWS is enabled.'\n )\n }\n if (!config.aws.accessKeyId || !config.aws.secretAccessKey) {\n throw new Error(\n '[notice-utility] AWS \"accessKeyId\" and \"secretAccessKey\" are required when AWS is enabled.'\n )\n }\n }\n\n // Validate local config if enabled\n if (config.local?.enabled) {\n if (!config.local.backupPath) {\n throw new Error(\n '[notice-utility] Local \"backupPath\" is required when local backup is enabled.'\n )\n }\n }\n\n // Store config\n setConfig(config)\n\n // Register global exception handlers\n registerExceptionHandlers()\n\n console.log(\n `[notice-utility] Initialized successfully (db: ${config.dbType}, service: ${config.serviceName || 'default'}, env: ${config.environment || process.env.NODE_ENV || 'development'})`\n )\n}\n","import type { Request, Response, NextFunction } from 'express'\nimport { getConfig } from '../utils/config'\nimport { enqueue } from '../utils/queue'\n\n/**\n * Express middleware factory for request/response logging.\n * Captures method, URL, headers, body, response, timing, user, IP.\n * All logging is non-blocking via the async queue.\n */\nexport function requestLogger() {\n return (req: Request, res: Response, next: NextFunction): void => {\n const startTime = Date.now()\n\n // Capture the original res.json to intercept the response body\n const originalJson = res.json.bind(res)\n let responseBody: unknown = undefined\n\n res.json = function (body: any) {\n responseBody = body\n return originalJson(body)\n }\n\n // Hook into the 'finish' event to log after response is sent\n res.on('finish', () => {\n try {\n const config = getConfig()\n const responseTime = Date.now() - startTime\n\n const logEntry: Record<string, unknown> = {\n method: req.method,\n url: req.originalUrl || req.url,\n headers: req.headers,\n requestBody: req.body || null,\n responseStatus: res.statusCode,\n responseBody: responseBody ?? null,\n responseTime,\n userId: (req as any).user?.id || (req as any).user?._id || (req as any).userId || null,\n ipAddress:\n (req.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() ||\n req.socket?.remoteAddress ||\n 'unknown',\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.requestLogs, logEntry)\n } catch {\n // Silent — logging failure must never affect the request\n }\n })\n\n next()\n }\n}\n","import { exec } from 'child_process'\nimport { promisify } from 'util'\nimport * as path from 'path'\nimport * as fs from 'fs'\nimport { getConfig } from '../utils/config'\nimport { getModel, getDbUri } from '../utils/db'\n\nconst execAsync = promisify(exec)\n\n/**\n * Generate a timestamp-based backup file name.\n */\nfunction getBackupFileName(): string {\n const now = new Date()\n const ts = now.toISOString().replace(/[:.]/g, '-')\n return `backup-${ts}`\n}\n\n/**\n * Perform a full MongoDB backup using mongodump.\n * Saves to local filesystem and/or uploads to S3 based on config.\n */\nasync function performBackup(): Promise<void> {\n const config = getConfig()\n const dbUri = getDbUri()\n const fileName = getBackupFileName()\n const archiveName = `${fileName}.zip`\n\n // Temp directory for backup\n const tempDir = path.resolve(process.cwd(), '.notice-backups-tmp')\n if (!fs.existsSync(tempDir)) {\n fs.mkdirSync(tempDir, { recursive: true })\n }\n\n const dumpDir = path.join(tempDir, fileName)\n const archivePath = path.join(tempDir, archiveName)\n\n try {\n // Run mongodump to a directory\n console.log(`[notice-utility] Starting backup: ${archiveName}`)\n await execAsync(`mongodump --uri=\"${dbUri}\" --out=\"${dumpDir}\"`)\n\n // Zip the directory\n await execAsync(`zip -r \"${archivePath}\" \"${fileName}\"`, { cwd: tempDir })\n\n const stats = fs.statSync(archivePath)\n const fileSize = stats.size\n\n // ── Save to local storage ──\n if (config.local?.enabled) {\n const localDir = path.resolve(process.cwd(), config.local.backupPath)\n if (!fs.existsSync(localDir)) {\n fs.mkdirSync(localDir, { recursive: true })\n }\n const localPath = path.join(localDir, archiveName)\n fs.copyFileSync(archivePath, localPath)\n console.log(`[notice-utility] Backup saved locally: ${localPath}`)\n\n // Log to database\n await logBackup({\n backupFileName: archiveName,\n backupType: 'full',\n location: 'local',\n fileSize,\n status: 'success',\n })\n }\n\n // ── Upload to S3 ──\n if (config.aws?.enabled) {\n await uploadToS3(archivePath, archiveName, fileSize)\n }\n\n // Cleanup temp files\n if (fs.existsSync(archivePath)) {\n fs.unlinkSync(archivePath)\n }\n if (fs.existsSync(dumpDir)) {\n fs.rmSync(dumpDir, { recursive: true, force: true })\n }\n\n console.log(`[notice-utility] Backup completed: ${archiveName}`)\n } catch (err) {\n console.error('[notice-utility] Backup failed:', err instanceof Error ? err.message : err)\n\n await logBackup({\n backupFileName: archiveName,\n backupType: 'full',\n location: config.aws?.enabled ? 's3' : 'local',\n fileSize: 0,\n status: 'failed',\n errorMessage: err instanceof Error ? err.message : String(err),\n }).catch(() => {})\n\n // Don't rethrow — backup failure should not crash the host app\n }\n}\n\n/**\n * Upload backup archive to AWS S3.\n */\nasync function uploadToS3(filePath: string, fileName: string, fileSize: number): Promise<void> {\n const config = getConfig()\n if (!config.aws) throw new Error('AWS config not provided')\n\n const { S3Client, PutObjectCommand } = await import('@aws-sdk/client-s3')\n\n const s3Client = new S3Client({\n region: config.aws.region,\n credentials: {\n accessKeyId: config.aws.accessKeyId,\n secretAccessKey: config.aws.secretAccessKey,\n },\n })\n\n const fileBuffer = fs.readFileSync(filePath)\n\n const command = new PutObjectCommand({\n Bucket: config.aws.bucketName,\n Key: `backups/${fileName}`,\n Body: fileBuffer,\n ContentType: 'application/zip',\n })\n\n await s3Client.send(command)\n console.log(`[notice-utility] Backup uploaded to S3: backups/${fileName}`)\n\n await logBackup({\n backupFileName: fileName,\n backupType: 'full',\n location: 's3',\n fileSize,\n status: 'success',\n })\n}\n\n/**\n * Write a backup log entry to the configured backupLogs collection.\n */\nasync function logBackup(entry: {\n backupFileName: string\n backupType: string\n location: 'local' | 's3'\n fileSize: number\n status: 'success' | 'failed'\n errorMessage?: string\n}): Promise<void> {\n try {\n const config = getConfig()\n const Model = getModel(config.tables.backupLogs)\n await Model.create({\n ...entry,\n createdAt: new Date(),\n })\n } catch (err) {\n console.error(\n '[notice-utility] Failed to write backup log:',\n err instanceof Error ? err.message : err\n )\n }\n}\n\n/**\n * Public export — trigger a manual database backup.\n */\nexport async function manualBackupTrigger(): Promise<void> {\n await performBackup()\n}\n"],"mappings":"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","names":["mongoose","Model","Model"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/utils/config.ts","../src/utils/db.ts","../src/utils/queue.ts","../src/exceptions/index.ts","../src/init.ts","../src/logger/index.ts","../src/backup/index.ts","../src/schedulers/backup.ts","../src/schedulers/cleanup.ts"],"sourcesContent":["// ─── Public API ─────────────────────────────────────────────────────────────\n\nexport { initializeNoticePackage } from './init'\nexport { requestLogger } from './logger'\nexport { manualBackupTrigger } from './backup'\nexport { logCustomError, errorMiddleware } from './exceptions'\n\n// ─── Schedulers (requires node-cron peer dependency) ────────────────────────\n\nexport { initBackupScheduler } from './schedulers/backup'\nexport { initLogCleanupScheduler } from './schedulers/cleanup'\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport type {\n NoticeConfig,\n AWSConfig,\n LocalConfig,\n NoticeTables,\n CronConfig,\n RequestLogEntry,\n ErrorLogEntry,\n BackupLogEntry,\n} from './types'\n","import type { NoticeConfig } from '../types'\n\nlet _config: NoticeConfig | null = null\n\nexport function setConfig(config: NoticeConfig): void {\n _config = { ...config }\n}\n\nexport function getConfig(): NoticeConfig {\n if (!_config) {\n throw new Error(\n '[db-backup-logging] Package not initialized. Call initializeNoticePackage() first.'\n )\n }\n return _config\n}\n","import mongoose, { Schema, Model, Connection } from 'mongoose'\nimport { getConfig } from './config'\n\nconst modelCache = new Map<string, Model<any>>()\n\nlet localConnection: Connection | null = null\n\nexport function getDbConnection(): Connection {\n if (!localConnection) {\n localConnection = mongoose.createConnection(getConfig().dbUri)\n }\n return localConnection\n}\n\n/**\n * Gets or creates a Mongoose model for the given collection name.\n * Uses a flexible mixed schema to support any log structure.\n */\nexport function getModel(collectionName: string): Model<any> {\n if (modelCache.has(collectionName)) {\n return modelCache.get(collectionName)!\n }\n\n const schema = new Schema(\n {},\n {\n strict: false,\n timestamps: false,\n collection: collectionName,\n }\n )\n\n // Use a dedicated connection for db-backup-logging to avoid 'npm link' duplication issues\n const conn: Connection = getDbConnection()\n\n // Avoid OverwriteModelError — check if model already exists\n let model: Model<any>\n try {\n model = conn.model(collectionName)\n } catch {\n model = conn.model(collectionName, schema)\n }\n\n modelCache.set(collectionName, model)\n return model\n}\n\n/**\n * Creates a separate Mongoose connection for backup purposes (mongodump URI).\n */\nexport function getDbUri(): string {\n return getConfig().dbUri\n}\n","import { getModel } from './db'\n\ninterface QueueItem {\n collectionName: string\n data: Record<string, unknown>\n}\n\nconst queue: QueueItem[] = []\nlet isProcessing = false\nconst BATCH_SIZE = 50\nconst FLUSH_INTERVAL_MS = 5000\nconst MAX_RETRIES = 3\n\nlet flushTimer: ReturnType<typeof setInterval> | null = null\n\n/**\n * Enqueue a log entry for async writing to the database.\n * This never throws — all errors are silently logged to console.\n */\nexport function enqueue(collectionName: string, data: Record<string, unknown>): void {\n queue.push({ collectionName, data })\n\n // Start the flush interval if not already running\n if (!flushTimer) {\n flushTimer = setInterval(() => {\n flush().catch(() => {})\n }, FLUSH_INTERVAL_MS)\n\n // Unref so the timer doesn't keep the process alive\n if (flushTimer && typeof flushTimer === 'object' && 'unref' in flushTimer) {\n flushTimer.unref()\n }\n }\n\n // If queue reaches batch size, flush immediately\n if (queue.length >= BATCH_SIZE) {\n flush().catch(() => {})\n }\n}\n\n/**\n * Flush all queued log entries to the database.\n */\nexport async function flush(): Promise<void> {\n if (isProcessing || queue.length === 0) return\n isProcessing = true\n\n const batch = queue.splice(0, BATCH_SIZE)\n\n // Group by collection\n const grouped = new Map<string, Record<string, unknown>[]>()\n for (const item of batch) {\n const list = grouped.get(item.collectionName) || []\n list.push(item.data)\n grouped.set(item.collectionName, list)\n }\n\n for (const [collectionName, docs] of grouped) {\n let retries = 0\n while (retries < MAX_RETRIES) {\n try {\n const Model = getModel(collectionName)\n await Model.insertMany(docs, { ordered: false })\n break\n } catch (err) {\n retries++\n if (retries >= MAX_RETRIES) {\n console.error(\n `[db-backup-logging] Failed to write ${docs.length} logs to \"${collectionName}\" after ${MAX_RETRIES} retries:`,\n err instanceof Error ? err.message : err\n )\n } else {\n // Brief delay before retry\n await new Promise((r) => setTimeout(r, 100 * retries))\n }\n }\n }\n }\n\n isProcessing = false\n\n // If there are more items in the queue, continue flushing\n if (queue.length > 0) {\n flush().catch(() => {})\n }\n}\n","import type { Request, Response, NextFunction } from 'express'\nimport { getConfig } from '../utils/config'\nimport { enqueue } from '../utils/queue'\n\n/**\n * Log an error entry to the configured errorLogs collection.\n */\nfunction logError(\n error: Error | string,\n context?: {\n req?: Request\n userId?: string\n }\n): void {\n try {\n const config = getConfig()\n const err = typeof error === 'string' ? new Error(error) : error\n\n const logEntry: Record<string, unknown> = {\n errorMessage: err.message,\n stackTrace: err.stack || null,\n requestContext: context?.req\n ? {\n method: context.req.method,\n url: context.req.originalUrl || context.req.url,\n headers: context.req.headers,\n body: context.req.body || null,\n }\n : null,\n userId:\n context?.userId ||\n (context?.req as any)?.user?.id ||\n (context?.req as any)?.user?._id ||\n null,\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.errorLogs, logEntry)\n } catch {\n // Silent — error logging failure must never cause further errors\n }\n}\n\n/**\n * Register global process-level exception handlers.\n * Chains with existing handlers — does NOT override them.\n */\nexport function registerExceptionHandlers(): void {\n // Store references to any existing handlers so we can chain\n const existingUncaught = process.listeners('uncaughtException').slice()\n const existingRejection = process.listeners('unhandledRejection').slice()\n\n // uncaughtException\n process.on('uncaughtException', (error: Error) => {\n logError(error)\n // Don't prevent the default crash behavior for truly unrecoverable errors\n // The existing handlers will still fire since we're adding, not replacing\n })\n\n // unhandledRejection\n process.on('unhandledRejection', (reason: unknown) => {\n const error = reason instanceof Error ? reason : new Error(String(reason))\n logError(error)\n })\n}\n\n/**\n * Express error-handling middleware.\n * Must be registered AFTER all routes: app.use(errorMiddleware())\n */\nexport function errorMiddleware() {\n return (err: Error, req: Request, res: Response, next: NextFunction): void => {\n logError(err, { req })\n\n // Pass to the next error handler (don't swallow the error)\n next(err)\n }\n}\n\n/**\n * Public export — manually log a custom error with optional context.\n */\nexport function logCustomError(\n error: Error | string,\n context?: {\n userId?: string\n requestContext?: {\n method?: string\n url?: string\n headers?: Record<string, string | string[] | undefined>\n body?: unknown\n }\n }\n): void {\n try {\n const config = getConfig()\n const err = typeof error === 'string' ? new Error(error) : error\n\n const logEntry: Record<string, unknown> = {\n errorMessage: err.message,\n stackTrace: err.stack || null,\n requestContext: context?.requestContext || null,\n userId: context?.userId || null,\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.errorLogs, logEntry)\n } catch {\n // Silent\n }\n}\n","import type { NoticeConfig } from './types'\nimport { setConfig } from './utils/config'\nimport { registerExceptionHandlers } from './exceptions'\n\n/**\n * Initialize the db-backup-logging package.\n * Must be called once during application startup, after mongoose.connect().\n *\n * @param config - Full package configuration\n */\nexport function initializeNoticePackage(config: NoticeConfig): void {\n // Validate required fields\n if (!config.dbType) {\n throw new Error('[db-backup-logging] \"dbType\" is required in config.')\n }\n if (!config.dbUri) {\n throw new Error('[db-backup-logging] \"dbUri\" is required in config.')\n }\n if (!config.tables) {\n throw new Error('[db-backup-logging] \"tables\" configuration is required.')\n }\n if (!config.tables.requestLogs || !config.tables.errorLogs || !config.tables.backupLogs) {\n throw new Error(\n '[db-backup-logging] All table names (requestLogs, errorLogs, backupLogs) must be configured.'\n )\n }\n\n // Validate AWS config if enabled\n if (config.aws?.enabled) {\n if (!config.aws.bucketName || !config.aws.region) {\n throw new Error(\n '[db-backup-logging] AWS \"bucketName\" and \"region\" are required when AWS is enabled.'\n )\n }\n if (!config.aws.accessKeyId || !config.aws.secretAccessKey) {\n throw new Error(\n '[db-backup-logging] AWS \"accessKeyId\" and \"secretAccessKey\" are required when AWS is enabled.'\n )\n }\n\n try {\n require.resolve('@aws-sdk/client-s3')\n } catch {\n throw new Error(\n '[db-backup-logging] AWS backup is enabled in config, but \"@aws-sdk/client-s3\" is not installed. Please run: npm install @aws-sdk/client-s3'\n )\n }\n }\n\n // Validate local config if enabled\n if (config.local?.enabled) {\n if (!config.local.backupPath) {\n throw new Error(\n '[db-backup-logging] Local \"backupPath\" is required when local backup is enabled.'\n )\n }\n }\n\n // Store config\n setConfig(config)\n\n // Register global exception handlers\n registerExceptionHandlers()\n\n console.log(\n `[db-backup-logging] Initialized successfully (db: ${config.dbType}, service: ${config.serviceName || 'default'}, env: ${config.environment || process.env.NODE_ENV || 'development'})`\n )\n}\n","import type { Request, Response, NextFunction } from 'express'\nimport { getConfig } from '../utils/config'\nimport { enqueue } from '../utils/queue'\n\n/**\n * Express middleware factory for request/response logging.\n * Captures method, URL, headers, body, response, timing, user, IP.\n * All logging is non-blocking via the async queue.\n */\nexport function requestLogger() {\n return (req: Request, res: Response, next: NextFunction): void => {\n const startTime = Date.now()\n\n // Capture the original res.json to intercept the response body\n const originalJson = res.json.bind(res)\n let responseBody: unknown = undefined\n\n res.json = function (body: any) {\n responseBody = body\n return originalJson(body)\n }\n\n // Hook into the 'finish' event to log after response is sent\n res.on('finish', () => {\n try {\n const config = getConfig()\n const responseTime = Date.now() - startTime\n\n const logEntry: Record<string, unknown> = {\n method: req.method,\n url: req.originalUrl || req.url,\n headers: req.headers,\n requestBody: req.body || null,\n responseStatus: res.statusCode,\n responseBody: responseBody ?? null,\n responseTime,\n userId: (req as any).user?.id || (req as any).user?._id || (req as any).userId || null,\n ipAddress:\n (req.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() ||\n req.socket?.remoteAddress ||\n 'unknown',\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.requestLogs, logEntry)\n } catch {\n // Silent — logging failure must never affect the request\n }\n })\n\n next()\n }\n}\n","import { exec } from 'child_process'\nimport { promisify } from 'util'\nimport * as path from 'path'\nimport * as fs from 'fs'\nimport { getConfig } from '../utils/config'\nimport { getModel, getDbUri } from '../utils/db'\n\nconst execAsync = promisify(exec)\n\n/**\n * Generate a timestamp-based backup file name.\n */\nfunction getBackupFileName(): string {\n const now = new Date()\n const ts = now.toISOString().replace(/[:.]/g, '-')\n return `backup-${ts}`\n}\n\n/**\n * Perform a full MongoDB backup using mongodump.\n * Saves to local filesystem and/or uploads to S3 based on config.\n */\nasync function performBackup(): Promise<void> {\n const config = getConfig()\n const dbUri = getDbUri()\n const fileName = getBackupFileName()\n const archiveName = `${fileName}.zip`\n\n // Temp directory for backup\n const tempDir = path.resolve(process.cwd(), '.notice-backups-tmp')\n if (!fs.existsSync(tempDir)) {\n fs.mkdirSync(tempDir, { recursive: true })\n }\n\n const dumpDir = path.join(tempDir, fileName)\n const archivePath = path.join(tempDir, archiveName)\n\n try {\n // Run mongodump to a directory\n console.log(`[db-backup-logging] Starting backup: ${archiveName}`)\n await execAsync(`mongodump --uri=\"${dbUri}\" --out=\"${dumpDir}\"`)\n\n // Zip the directory\n await execAsync(`zip -r \"${archivePath}\" \"${fileName}\"`, { cwd: tempDir })\n\n const stats = fs.statSync(archivePath)\n const fileSize = stats.size\n\n // ── Save to local storage ──\n if (config.local?.enabled) {\n const localDir = path.resolve(process.cwd(), config.local.backupPath)\n if (!fs.existsSync(localDir)) {\n fs.mkdirSync(localDir, { recursive: true })\n }\n const localPath = path.join(localDir, archiveName)\n fs.copyFileSync(archivePath, localPath)\n console.log(`[db-backup-logging] Backup saved locally: ${localPath}`)\n\n // Log to database\n await logBackup({\n backupFileName: archiveName,\n backupType: 'full',\n location: 'local',\n fileSize,\n status: 'success',\n })\n }\n\n // ── Upload to S3 ──\n if (config.aws?.enabled) {\n await uploadToS3(archivePath, archiveName, fileSize)\n }\n\n // Cleanup temp files\n if (fs.existsSync(archivePath)) {\n fs.unlinkSync(archivePath)\n }\n if (fs.existsSync(dumpDir)) {\n fs.rmSync(dumpDir, { recursive: true, force: true })\n }\n\n console.log(`[db-backup-logging] Backup completed: ${archiveName}`)\n } catch (err) {\n console.error('[db-backup-logging] Backup failed:', err instanceof Error ? err.message : err)\n\n await logBackup({\n backupFileName: archiveName,\n backupType: 'full',\n location: config.aws?.enabled ? 's3' : 'local',\n fileSize: 0,\n status: 'failed',\n errorMessage: err instanceof Error ? err.message : String(err),\n }).catch(() => {})\n\n // Don't rethrow — backup failure should not crash the host app\n }\n}\n\n/**\n * Upload backup archive to AWS S3.\n */\nasync function uploadToS3(filePath: string, fileName: string, fileSize: number): Promise<void> {\n const config = getConfig()\n if (!config.aws) throw new Error('AWS config not provided')\n\n const { S3Client, PutObjectCommand } = await import('@aws-sdk/client-s3')\n\n const s3Client = new S3Client({\n region: config.aws.region,\n credentials: {\n accessKeyId: config.aws.accessKeyId,\n secretAccessKey: config.aws.secretAccessKey,\n },\n })\n\n const fileBuffer = fs.readFileSync(filePath)\n\n const command = new PutObjectCommand({\n Bucket: config.aws.bucketName,\n Key: `backups/${fileName}`,\n Body: fileBuffer,\n ContentType: 'application/zip',\n })\n\n await s3Client.send(command)\n console.log(`[db-backup-logging] Backup uploaded to S3: backups/${fileName}`)\n\n await logBackup({\n backupFileName: fileName,\n backupType: 'full',\n location: 's3',\n fileSize,\n status: 'success',\n })\n}\n\n/**\n * Write a backup log entry to the configured backupLogs collection.\n */\nasync function logBackup(entry: {\n backupFileName: string\n backupType: string\n location: 'local' | 's3'\n fileSize: number\n status: 'success' | 'failed'\n errorMessage?: string\n}): Promise<void> {\n try {\n const config = getConfig()\n const Model = getModel(config.tables.backupLogs)\n await Model.create({\n ...entry,\n createdAt: new Date(),\n })\n } catch (err) {\n console.error(\n '[db-backup-logging] Failed to write backup log:',\n err instanceof Error ? err.message : err\n )\n }\n}\n\n/**\n * Public export — trigger a manual database backup.\n */\nexport async function manualBackupTrigger(): Promise<void> {\n await performBackup()\n}\n","import { getConfig } from '../utils/config'\nimport { manualBackupTrigger } from '../backup'\n\n/**\n * Dynamically resolve node-cron from the host project.\n * Throws a helpful error if not installed.\n */\nfunction loadNodeCron(): typeof import('node-cron') {\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n return require('node-cron')\n } catch {\n throw new Error(\n '[db-backup-logging] node-cron is required to use initBackupScheduler().\\n' +\n 'Please install it in your root project:\\n' +\n ' npm install node-cron\\n' +\n ' yarn add node-cron'\n )\n }\n}\n\n/**\n * Start an automatic backup cron job.\n *\n * Schedule and timezone are resolved in order:\n * 1. Environment variables BACKUP_CRON_SCHEDULE / BACKUP_TIMEZONE\n * 2. config.backupCron passed to initializeNoticePackage()\n * 3. Built-in defaults (\"0 2 * * *\" = 2 AM daily, UTC)\n *\n * Requires node-cron >= 3.0.0 installed in the host project.\n */\nexport function initBackupScheduler(): void {\n const cron = loadNodeCron()\n const config = getConfig()\n\n const schedule =\n process.env.BACKUP_CRON_SCHEDULE ||\n config.backupCron?.schedule ||\n '0 2 * * *'\n\n const timezone =\n process.env.BACKUP_TIMEZONE ||\n config.backupCron?.timezone ||\n 'UTC'\n\n if (!cron.validate(schedule)) {\n throw new Error(\n `[db-backup-logging] Invalid BACKUP_CRON_SCHEDULE: \"${schedule}\". ` +\n 'Please provide a valid cron expression.'\n )\n }\n\n cron.schedule(\n schedule,\n async () => {\n console.log('[db-backup-logging] Backup scheduler triggered.')\n await manualBackupTrigger()\n },\n { timezone }\n )\n\n console.log(\n `[db-backup-logging] Backup scheduler started — schedule: \"${schedule}\", timezone: \"${timezone}\"`\n )\n}\n","import { getConfig } from '../utils/config'\nimport { getModel } from '../utils/db'\n\n/**\n * Dynamically resolve node-cron from the host project.\n * Throws a helpful error if not installed.\n */\nfunction loadNodeCron(): typeof import('node-cron') {\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n return require('node-cron')\n } catch {\n throw new Error(\n '[db-backup-logging] node-cron is required to use initLogCleanupScheduler().\\n' +\n 'Please install it in your root project:\\n' +\n ' npm install node-cron\\n' +\n ' yarn add node-cron'\n )\n }\n}\n\n/**\n * Delete request/error log documents older than retentionDays.\n */\nasync function purgeOldLogs(retentionDays: number): Promise<void> {\n const config = getConfig()\n const cutoff = new Date()\n cutoff.setDate(cutoff.getDate() - retentionDays)\n\n const collections = [config.tables.requestLogs, config.tables.errorLogs]\n\n for (const collectionName of collections) {\n try {\n const Model = getModel(collectionName)\n const result = await Model.deleteMany({ createdAt: { $lt: cutoff } })\n console.log(\n `[db-backup-logging] Log cleanup: removed ${result.deletedCount} record(s) from \"${collectionName}\" older than ${retentionDays} day(s).`\n )\n } catch (err) {\n console.error(\n `[db-backup-logging] Log cleanup failed for \"${collectionName}\":`,\n err instanceof Error ? err.message : err\n )\n }\n }\n}\n\n/**\n * Start an automatic log cleanup cron job.\n *\n * Schedule, timezone and retention days are resolved in order:\n * 1. Environment variables LOG_CLEANUP_CRON_SCHEDULE / LOG_CLEANUP_TIMEZONE / LOG_RETENTION_DAYS\n * 2. config.logCleanupCron passed to initializeNoticePackage()\n * 3. Built-in defaults (\"0 3 * * *\" = 3 AM daily, UTC, 30 days retention)\n *\n * Requires node-cron >= 3.0.0 installed in the host project.\n */\nexport function initLogCleanupScheduler(): void {\n const cron = loadNodeCron()\n const config = getConfig()\n\n const schedule =\n process.env.LOG_CLEANUP_CRON_SCHEDULE ||\n config.logCleanupCron?.schedule ||\n '0 3 * * *'\n\n const timezone =\n process.env.LOG_CLEANUP_TIMEZONE ||\n config.logCleanupCron?.timezone ||\n 'UTC'\n\n const retentionDays =\n Number(process.env.LOG_RETENTION_DAYS) ||\n config.logCleanupCron?.retentionDays ||\n 30\n\n if (!cron.validate(schedule)) {\n throw new Error(\n `[db-backup-logging] Invalid LOG_CLEANUP_CRON_SCHEDULE: \"${schedule}\". ` +\n 'Please provide a valid cron expression.'\n )\n }\n\n cron.schedule(\n schedule,\n async () => {\n console.log('[db-backup-logging] Log cleanup scheduler triggered.')\n await purgeOldLogs(retentionDays)\n },\n { timezone }\n )\n\n console.log(\n `[db-backup-logging] Log cleanup scheduler started — schedule: \"${schedule}\", timezone: \"${timezone}\", retentionDays: ${retentionDays}`\n )\n}\n"],"mappings":"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","names":["mongoose","Model","Model","loadNodeCron","Model"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
1
8
|
// src/utils/config.ts
|
|
2
9
|
var _config = null;
|
|
3
10
|
function setConfig(config) {
|
|
@@ -6,7 +13,7 @@ function setConfig(config) {
|
|
|
6
13
|
function getConfig() {
|
|
7
14
|
if (!_config) {
|
|
8
15
|
throw new Error(
|
|
9
|
-
"[
|
|
16
|
+
"[db-backup-logging] Package not initialized. Call initializeNoticePackage() first."
|
|
10
17
|
);
|
|
11
18
|
}
|
|
12
19
|
return _config;
|
|
@@ -92,7 +99,7 @@ async function flush() {
|
|
|
92
99
|
retries++;
|
|
93
100
|
if (retries >= MAX_RETRIES) {
|
|
94
101
|
console.error(
|
|
95
|
-
`[
|
|
102
|
+
`[db-backup-logging] Failed to write ${docs.length} logs to "${collectionName}" after ${MAX_RETRIES} retries:`,
|
|
96
103
|
err instanceof Error ? err.message : err
|
|
97
104
|
);
|
|
98
105
|
} else {
|
|
@@ -169,42 +176,49 @@ function logCustomError(error, context) {
|
|
|
169
176
|
// src/init.ts
|
|
170
177
|
function initializeNoticePackage(config) {
|
|
171
178
|
if (!config.dbType) {
|
|
172
|
-
throw new Error('[
|
|
179
|
+
throw new Error('[db-backup-logging] "dbType" is required in config.');
|
|
173
180
|
}
|
|
174
181
|
if (!config.dbUri) {
|
|
175
|
-
throw new Error('[
|
|
182
|
+
throw new Error('[db-backup-logging] "dbUri" is required in config.');
|
|
176
183
|
}
|
|
177
184
|
if (!config.tables) {
|
|
178
|
-
throw new Error('[
|
|
185
|
+
throw new Error('[db-backup-logging] "tables" configuration is required.');
|
|
179
186
|
}
|
|
180
187
|
if (!config.tables.requestLogs || !config.tables.errorLogs || !config.tables.backupLogs) {
|
|
181
188
|
throw new Error(
|
|
182
|
-
"[
|
|
189
|
+
"[db-backup-logging] All table names (requestLogs, errorLogs, backupLogs) must be configured."
|
|
183
190
|
);
|
|
184
191
|
}
|
|
185
192
|
if (config.aws?.enabled) {
|
|
186
193
|
if (!config.aws.bucketName || !config.aws.region) {
|
|
187
194
|
throw new Error(
|
|
188
|
-
'[
|
|
195
|
+
'[db-backup-logging] AWS "bucketName" and "region" are required when AWS is enabled.'
|
|
189
196
|
);
|
|
190
197
|
}
|
|
191
198
|
if (!config.aws.accessKeyId || !config.aws.secretAccessKey) {
|
|
192
199
|
throw new Error(
|
|
193
|
-
'[
|
|
200
|
+
'[db-backup-logging] AWS "accessKeyId" and "secretAccessKey" are required when AWS is enabled.'
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
__require.resolve("@aws-sdk/client-s3");
|
|
205
|
+
} catch {
|
|
206
|
+
throw new Error(
|
|
207
|
+
'[db-backup-logging] AWS backup is enabled in config, but "@aws-sdk/client-s3" is not installed. Please run: npm install @aws-sdk/client-s3'
|
|
194
208
|
);
|
|
195
209
|
}
|
|
196
210
|
}
|
|
197
211
|
if (config.local?.enabled) {
|
|
198
212
|
if (!config.local.backupPath) {
|
|
199
213
|
throw new Error(
|
|
200
|
-
'[
|
|
214
|
+
'[db-backup-logging] Local "backupPath" is required when local backup is enabled.'
|
|
201
215
|
);
|
|
202
216
|
}
|
|
203
217
|
}
|
|
204
218
|
setConfig(config);
|
|
205
219
|
registerExceptionHandlers();
|
|
206
220
|
console.log(
|
|
207
|
-
`[
|
|
221
|
+
`[db-backup-logging] Initialized successfully (db: ${config.dbType}, service: ${config.serviceName || "default"}, env: ${config.environment || process.env.NODE_ENV || "development"})`
|
|
208
222
|
);
|
|
209
223
|
}
|
|
210
224
|
|
|
@@ -267,7 +281,7 @@ async function performBackup() {
|
|
|
267
281
|
const dumpDir = path.join(tempDir, fileName);
|
|
268
282
|
const archivePath = path.join(tempDir, archiveName);
|
|
269
283
|
try {
|
|
270
|
-
console.log(`[
|
|
284
|
+
console.log(`[db-backup-logging] Starting backup: ${archiveName}`);
|
|
271
285
|
await execAsync(`mongodump --uri="${dbUri}" --out="${dumpDir}"`);
|
|
272
286
|
await execAsync(`zip -r "${archivePath}" "${fileName}"`, { cwd: tempDir });
|
|
273
287
|
const stats = fs.statSync(archivePath);
|
|
@@ -279,7 +293,7 @@ async function performBackup() {
|
|
|
279
293
|
}
|
|
280
294
|
const localPath = path.join(localDir, archiveName);
|
|
281
295
|
fs.copyFileSync(archivePath, localPath);
|
|
282
|
-
console.log(`[
|
|
296
|
+
console.log(`[db-backup-logging] Backup saved locally: ${localPath}`);
|
|
283
297
|
await logBackup({
|
|
284
298
|
backupFileName: archiveName,
|
|
285
299
|
backupType: "full",
|
|
@@ -297,9 +311,9 @@ async function performBackup() {
|
|
|
297
311
|
if (fs.existsSync(dumpDir)) {
|
|
298
312
|
fs.rmSync(dumpDir, { recursive: true, force: true });
|
|
299
313
|
}
|
|
300
|
-
console.log(`[
|
|
314
|
+
console.log(`[db-backup-logging] Backup completed: ${archiveName}`);
|
|
301
315
|
} catch (err) {
|
|
302
|
-
console.error("[
|
|
316
|
+
console.error("[db-backup-logging] Backup failed:", err instanceof Error ? err.message : err);
|
|
303
317
|
await logBackup({
|
|
304
318
|
backupFileName: archiveName,
|
|
305
319
|
backupType: "full",
|
|
@@ -330,7 +344,7 @@ async function uploadToS3(filePath, fileName, fileSize) {
|
|
|
330
344
|
ContentType: "application/zip"
|
|
331
345
|
});
|
|
332
346
|
await s3Client.send(command);
|
|
333
|
-
console.log(`[
|
|
347
|
+
console.log(`[db-backup-logging] Backup uploaded to S3: backups/${fileName}`);
|
|
334
348
|
await logBackup({
|
|
335
349
|
backupFileName: fileName,
|
|
336
350
|
backupType: "full",
|
|
@@ -349,7 +363,7 @@ async function logBackup(entry) {
|
|
|
349
363
|
});
|
|
350
364
|
} catch (err) {
|
|
351
365
|
console.error(
|
|
352
|
-
"[
|
|
366
|
+
"[db-backup-logging] Failed to write backup log:",
|
|
353
367
|
err instanceof Error ? err.message : err
|
|
354
368
|
);
|
|
355
369
|
}
|
|
@@ -357,8 +371,97 @@ async function logBackup(entry) {
|
|
|
357
371
|
async function manualBackupTrigger() {
|
|
358
372
|
await performBackup();
|
|
359
373
|
}
|
|
374
|
+
|
|
375
|
+
// src/schedulers/backup.ts
|
|
376
|
+
function loadNodeCron() {
|
|
377
|
+
try {
|
|
378
|
+
return __require("node-cron");
|
|
379
|
+
} catch {
|
|
380
|
+
throw new Error(
|
|
381
|
+
"[db-backup-logging] node-cron is required to use initBackupScheduler().\nPlease install it in your root project:\n npm install node-cron\n yarn add node-cron"
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function initBackupScheduler() {
|
|
386
|
+
const cron = loadNodeCron();
|
|
387
|
+
const config = getConfig();
|
|
388
|
+
const schedule = process.env.BACKUP_CRON_SCHEDULE || config.backupCron?.schedule || "0 2 * * *";
|
|
389
|
+
const timezone = process.env.BACKUP_TIMEZONE || config.backupCron?.timezone || "UTC";
|
|
390
|
+
if (!cron.validate(schedule)) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
`[db-backup-logging] Invalid BACKUP_CRON_SCHEDULE: "${schedule}". Please provide a valid cron expression.`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
cron.schedule(
|
|
396
|
+
schedule,
|
|
397
|
+
async () => {
|
|
398
|
+
console.log("[db-backup-logging] Backup scheduler triggered.");
|
|
399
|
+
await manualBackupTrigger();
|
|
400
|
+
},
|
|
401
|
+
{ timezone }
|
|
402
|
+
);
|
|
403
|
+
console.log(
|
|
404
|
+
`[db-backup-logging] Backup scheduler started \u2014 schedule: "${schedule}", timezone: "${timezone}"`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/schedulers/cleanup.ts
|
|
409
|
+
function loadNodeCron2() {
|
|
410
|
+
try {
|
|
411
|
+
return __require("node-cron");
|
|
412
|
+
} catch {
|
|
413
|
+
throw new Error(
|
|
414
|
+
"[db-backup-logging] node-cron is required to use initLogCleanupScheduler().\nPlease install it in your root project:\n npm install node-cron\n yarn add node-cron"
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
async function purgeOldLogs(retentionDays) {
|
|
419
|
+
const config = getConfig();
|
|
420
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
421
|
+
cutoff.setDate(cutoff.getDate() - retentionDays);
|
|
422
|
+
const collections = [config.tables.requestLogs, config.tables.errorLogs];
|
|
423
|
+
for (const collectionName of collections) {
|
|
424
|
+
try {
|
|
425
|
+
const Model2 = getModel(collectionName);
|
|
426
|
+
const result = await Model2.deleteMany({ createdAt: { $lt: cutoff } });
|
|
427
|
+
console.log(
|
|
428
|
+
`[db-backup-logging] Log cleanup: removed ${result.deletedCount} record(s) from "${collectionName}" older than ${retentionDays} day(s).`
|
|
429
|
+
);
|
|
430
|
+
} catch (err) {
|
|
431
|
+
console.error(
|
|
432
|
+
`[db-backup-logging] Log cleanup failed for "${collectionName}":`,
|
|
433
|
+
err instanceof Error ? err.message : err
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function initLogCleanupScheduler() {
|
|
439
|
+
const cron = loadNodeCron2();
|
|
440
|
+
const config = getConfig();
|
|
441
|
+
const schedule = process.env.LOG_CLEANUP_CRON_SCHEDULE || config.logCleanupCron?.schedule || "0 3 * * *";
|
|
442
|
+
const timezone = process.env.LOG_CLEANUP_TIMEZONE || config.logCleanupCron?.timezone || "UTC";
|
|
443
|
+
const retentionDays = Number(process.env.LOG_RETENTION_DAYS) || config.logCleanupCron?.retentionDays || 30;
|
|
444
|
+
if (!cron.validate(schedule)) {
|
|
445
|
+
throw new Error(
|
|
446
|
+
`[db-backup-logging] Invalid LOG_CLEANUP_CRON_SCHEDULE: "${schedule}". Please provide a valid cron expression.`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
cron.schedule(
|
|
450
|
+
schedule,
|
|
451
|
+
async () => {
|
|
452
|
+
console.log("[db-backup-logging] Log cleanup scheduler triggered.");
|
|
453
|
+
await purgeOldLogs(retentionDays);
|
|
454
|
+
},
|
|
455
|
+
{ timezone }
|
|
456
|
+
);
|
|
457
|
+
console.log(
|
|
458
|
+
`[db-backup-logging] Log cleanup scheduler started \u2014 schedule: "${schedule}", timezone: "${timezone}", retentionDays: ${retentionDays}`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
360
461
|
export {
|
|
361
462
|
errorMiddleware,
|
|
463
|
+
initBackupScheduler,
|
|
464
|
+
initLogCleanupScheduler,
|
|
362
465
|
initializeNoticePackage,
|
|
363
466
|
logCustomError,
|
|
364
467
|
manualBackupTrigger,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/config.ts","../src/utils/db.ts","../src/utils/queue.ts","../src/exceptions/index.ts","../src/init.ts","../src/logger/index.ts","../src/backup/index.ts"],"sourcesContent":["import type { NoticeConfig } from '../types'\n\nlet _config: NoticeConfig | null = null\n\nexport function setConfig(config: NoticeConfig): void {\n _config = { ...config }\n}\n\nexport function getConfig(): NoticeConfig {\n if (!_config) {\n throw new Error(\n '[notice-utility] Package not initialized. Call initializeNoticePackage() first.'\n )\n }\n return _config\n}\n","import mongoose, { Schema, Model, Connection } from 'mongoose'\nimport { getConfig } from './config'\n\nconst modelCache = new Map<string, Model<any>>()\n\nlet localConnection: Connection | null = null\n\nexport function getDbConnection(): Connection {\n if (!localConnection) {\n localConnection = mongoose.createConnection(getConfig().dbUri)\n }\n return localConnection\n}\n\n/**\n * Gets or creates a Mongoose model for the given collection name.\n * Uses a flexible mixed schema to support any log structure.\n */\nexport function getModel(collectionName: string): Model<any> {\n if (modelCache.has(collectionName)) {\n return modelCache.get(collectionName)!\n }\n\n const schema = new Schema(\n {},\n {\n strict: false,\n timestamps: false,\n collection: collectionName,\n }\n )\n\n // Use a dedicated connection for notice-utility to avoid 'npm link' duplication issues\n const conn: Connection = getDbConnection()\n\n // Avoid OverwriteModelError — check if model already exists\n let model: Model<any>\n try {\n model = conn.model(collectionName)\n } catch {\n model = conn.model(collectionName, schema)\n }\n\n modelCache.set(collectionName, model)\n return model\n}\n\n/**\n * Creates a separate Mongoose connection for backup purposes (mongodump URI).\n */\nexport function getDbUri(): string {\n return getConfig().dbUri\n}\n","import { getModel } from './db'\n\ninterface QueueItem {\n collectionName: string\n data: Record<string, unknown>\n}\n\nconst queue: QueueItem[] = []\nlet isProcessing = false\nconst BATCH_SIZE = 50\nconst FLUSH_INTERVAL_MS = 5000\nconst MAX_RETRIES = 3\n\nlet flushTimer: ReturnType<typeof setInterval> | null = null\n\n/**\n * Enqueue a log entry for async writing to the database.\n * This never throws — all errors are silently logged to console.\n */\nexport function enqueue(collectionName: string, data: Record<string, unknown>): void {\n queue.push({ collectionName, data })\n\n // Start the flush interval if not already running\n if (!flushTimer) {\n flushTimer = setInterval(() => {\n flush().catch(() => {})\n }, FLUSH_INTERVAL_MS)\n\n // Unref so the timer doesn't keep the process alive\n if (flushTimer && typeof flushTimer === 'object' && 'unref' in flushTimer) {\n flushTimer.unref()\n }\n }\n\n // If queue reaches batch size, flush immediately\n if (queue.length >= BATCH_SIZE) {\n flush().catch(() => {})\n }\n}\n\n/**\n * Flush all queued log entries to the database.\n */\nexport async function flush(): Promise<void> {\n if (isProcessing || queue.length === 0) return\n isProcessing = true\n\n const batch = queue.splice(0, BATCH_SIZE)\n\n // Group by collection\n const grouped = new Map<string, Record<string, unknown>[]>()\n for (const item of batch) {\n const list = grouped.get(item.collectionName) || []\n list.push(item.data)\n grouped.set(item.collectionName, list)\n }\n\n for (const [collectionName, docs] of grouped) {\n let retries = 0\n while (retries < MAX_RETRIES) {\n try {\n const Model = getModel(collectionName)\n await Model.insertMany(docs, { ordered: false })\n break\n } catch (err) {\n retries++\n if (retries >= MAX_RETRIES) {\n console.error(\n `[notice-utility] Failed to write ${docs.length} logs to \"${collectionName}\" after ${MAX_RETRIES} retries:`,\n err instanceof Error ? err.message : err\n )\n } else {\n // Brief delay before retry\n await new Promise((r) => setTimeout(r, 100 * retries))\n }\n }\n }\n }\n\n isProcessing = false\n\n // If there are more items in the queue, continue flushing\n if (queue.length > 0) {\n flush().catch(() => {})\n }\n}\n","import type { Request, Response, NextFunction } from 'express'\nimport { getConfig } from '../utils/config'\nimport { enqueue } from '../utils/queue'\n\n/**\n * Log an error entry to the configured errorLogs collection.\n */\nfunction logError(\n error: Error | string,\n context?: {\n req?: Request\n userId?: string\n }\n): void {\n try {\n const config = getConfig()\n const err = typeof error === 'string' ? new Error(error) : error\n\n const logEntry: Record<string, unknown> = {\n errorMessage: err.message,\n stackTrace: err.stack || null,\n requestContext: context?.req\n ? {\n method: context.req.method,\n url: context.req.originalUrl || context.req.url,\n headers: context.req.headers,\n body: context.req.body || null,\n }\n : null,\n userId:\n context?.userId ||\n (context?.req as any)?.user?.id ||\n (context?.req as any)?.user?._id ||\n null,\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.errorLogs, logEntry)\n } catch {\n // Silent — error logging failure must never cause further errors\n }\n}\n\n/**\n * Register global process-level exception handlers.\n * Chains with existing handlers — does NOT override them.\n */\nexport function registerExceptionHandlers(): void {\n // Store references to any existing handlers so we can chain\n const existingUncaught = process.listeners('uncaughtException').slice()\n const existingRejection = process.listeners('unhandledRejection').slice()\n\n // uncaughtException\n process.on('uncaughtException', (error: Error) => {\n logError(error)\n // Don't prevent the default crash behavior for truly unrecoverable errors\n // The existing handlers will still fire since we're adding, not replacing\n })\n\n // unhandledRejection\n process.on('unhandledRejection', (reason: unknown) => {\n const error = reason instanceof Error ? reason : new Error(String(reason))\n logError(error)\n })\n}\n\n/**\n * Express error-handling middleware.\n * Must be registered AFTER all routes: app.use(errorMiddleware())\n */\nexport function errorMiddleware() {\n return (err: Error, req: Request, res: Response, next: NextFunction): void => {\n logError(err, { req })\n\n // Pass to the next error handler (don't swallow the error)\n next(err)\n }\n}\n\n/**\n * Public export — manually log a custom error with optional context.\n */\nexport function logCustomError(\n error: Error | string,\n context?: {\n userId?: string\n requestContext?: {\n method?: string\n url?: string\n headers?: Record<string, string | string[] | undefined>\n body?: unknown\n }\n }\n): void {\n try {\n const config = getConfig()\n const err = typeof error === 'string' ? new Error(error) : error\n\n const logEntry: Record<string, unknown> = {\n errorMessage: err.message,\n stackTrace: err.stack || null,\n requestContext: context?.requestContext || null,\n userId: context?.userId || null,\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.errorLogs, logEntry)\n } catch {\n // Silent\n }\n}\n","import type { NoticeConfig } from './types'\nimport { setConfig } from './utils/config'\nimport { registerExceptionHandlers } from './exceptions'\n\n/**\n * Initialize the notice-utility package.\n * Must be called once during application startup, after mongoose.connect().\n *\n * @param config - Full package configuration\n */\nexport function initializeNoticePackage(config: NoticeConfig): void {\n // Validate required fields\n if (!config.dbType) {\n throw new Error('[notice-utility] \"dbType\" is required in config.')\n }\n if (!config.dbUri) {\n throw new Error('[notice-utility] \"dbUri\" is required in config.')\n }\n if (!config.tables) {\n throw new Error('[notice-utility] \"tables\" configuration is required.')\n }\n if (!config.tables.requestLogs || !config.tables.errorLogs || !config.tables.backupLogs) {\n throw new Error(\n '[notice-utility] All table names (requestLogs, errorLogs, backupLogs) must be configured.'\n )\n }\n\n // Validate AWS config if enabled\n if (config.aws?.enabled) {\n if (!config.aws.bucketName || !config.aws.region) {\n throw new Error(\n '[notice-utility] AWS \"bucketName\" and \"region\" are required when AWS is enabled.'\n )\n }\n if (!config.aws.accessKeyId || !config.aws.secretAccessKey) {\n throw new Error(\n '[notice-utility] AWS \"accessKeyId\" and \"secretAccessKey\" are required when AWS is enabled.'\n )\n }\n }\n\n // Validate local config if enabled\n if (config.local?.enabled) {\n if (!config.local.backupPath) {\n throw new Error(\n '[notice-utility] Local \"backupPath\" is required when local backup is enabled.'\n )\n }\n }\n\n // Store config\n setConfig(config)\n\n // Register global exception handlers\n registerExceptionHandlers()\n\n console.log(\n `[notice-utility] Initialized successfully (db: ${config.dbType}, service: ${config.serviceName || 'default'}, env: ${config.environment || process.env.NODE_ENV || 'development'})`\n )\n}\n","import type { Request, Response, NextFunction } from 'express'\nimport { getConfig } from '../utils/config'\nimport { enqueue } from '../utils/queue'\n\n/**\n * Express middleware factory for request/response logging.\n * Captures method, URL, headers, body, response, timing, user, IP.\n * All logging is non-blocking via the async queue.\n */\nexport function requestLogger() {\n return (req: Request, res: Response, next: NextFunction): void => {\n const startTime = Date.now()\n\n // Capture the original res.json to intercept the response body\n const originalJson = res.json.bind(res)\n let responseBody: unknown = undefined\n\n res.json = function (body: any) {\n responseBody = body\n return originalJson(body)\n }\n\n // Hook into the 'finish' event to log after response is sent\n res.on('finish', () => {\n try {\n const config = getConfig()\n const responseTime = Date.now() - startTime\n\n const logEntry: Record<string, unknown> = {\n method: req.method,\n url: req.originalUrl || req.url,\n headers: req.headers,\n requestBody: req.body || null,\n responseStatus: res.statusCode,\n responseBody: responseBody ?? null,\n responseTime,\n userId: (req as any).user?.id || (req as any).user?._id || (req as any).userId || null,\n ipAddress:\n (req.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() ||\n req.socket?.remoteAddress ||\n 'unknown',\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.requestLogs, logEntry)\n } catch {\n // Silent — logging failure must never affect the request\n }\n })\n\n next()\n }\n}\n","import { exec } from 'child_process'\nimport { promisify } from 'util'\nimport * as path from 'path'\nimport * as fs from 'fs'\nimport { getConfig } from '../utils/config'\nimport { getModel, getDbUri } from '../utils/db'\n\nconst execAsync = promisify(exec)\n\n/**\n * Generate a timestamp-based backup file name.\n */\nfunction getBackupFileName(): string {\n const now = new Date()\n const ts = now.toISOString().replace(/[:.]/g, '-')\n return `backup-${ts}`\n}\n\n/**\n * Perform a full MongoDB backup using mongodump.\n * Saves to local filesystem and/or uploads to S3 based on config.\n */\nasync function performBackup(): Promise<void> {\n const config = getConfig()\n const dbUri = getDbUri()\n const fileName = getBackupFileName()\n const archiveName = `${fileName}.zip`\n\n // Temp directory for backup\n const tempDir = path.resolve(process.cwd(), '.notice-backups-tmp')\n if (!fs.existsSync(tempDir)) {\n fs.mkdirSync(tempDir, { recursive: true })\n }\n\n const dumpDir = path.join(tempDir, fileName)\n const archivePath = path.join(tempDir, archiveName)\n\n try {\n // Run mongodump to a directory\n console.log(`[notice-utility] Starting backup: ${archiveName}`)\n await execAsync(`mongodump --uri=\"${dbUri}\" --out=\"${dumpDir}\"`)\n\n // Zip the directory\n await execAsync(`zip -r \"${archivePath}\" \"${fileName}\"`, { cwd: tempDir })\n\n const stats = fs.statSync(archivePath)\n const fileSize = stats.size\n\n // ── Save to local storage ──\n if (config.local?.enabled) {\n const localDir = path.resolve(process.cwd(), config.local.backupPath)\n if (!fs.existsSync(localDir)) {\n fs.mkdirSync(localDir, { recursive: true })\n }\n const localPath = path.join(localDir, archiveName)\n fs.copyFileSync(archivePath, localPath)\n console.log(`[notice-utility] Backup saved locally: ${localPath}`)\n\n // Log to database\n await logBackup({\n backupFileName: archiveName,\n backupType: 'full',\n location: 'local',\n fileSize,\n status: 'success',\n })\n }\n\n // ── Upload to S3 ──\n if (config.aws?.enabled) {\n await uploadToS3(archivePath, archiveName, fileSize)\n }\n\n // Cleanup temp files\n if (fs.existsSync(archivePath)) {\n fs.unlinkSync(archivePath)\n }\n if (fs.existsSync(dumpDir)) {\n fs.rmSync(dumpDir, { recursive: true, force: true })\n }\n\n console.log(`[notice-utility] Backup completed: ${archiveName}`)\n } catch (err) {\n console.error('[notice-utility] Backup failed:', err instanceof Error ? err.message : err)\n\n await logBackup({\n backupFileName: archiveName,\n backupType: 'full',\n location: config.aws?.enabled ? 's3' : 'local',\n fileSize: 0,\n status: 'failed',\n errorMessage: err instanceof Error ? err.message : String(err),\n }).catch(() => {})\n\n // Don't rethrow — backup failure should not crash the host app\n }\n}\n\n/**\n * Upload backup archive to AWS S3.\n */\nasync function uploadToS3(filePath: string, fileName: string, fileSize: number): Promise<void> {\n const config = getConfig()\n if (!config.aws) throw new Error('AWS config not provided')\n\n const { S3Client, PutObjectCommand } = await import('@aws-sdk/client-s3')\n\n const s3Client = new S3Client({\n region: config.aws.region,\n credentials: {\n accessKeyId: config.aws.accessKeyId,\n secretAccessKey: config.aws.secretAccessKey,\n },\n })\n\n const fileBuffer = fs.readFileSync(filePath)\n\n const command = new PutObjectCommand({\n Bucket: config.aws.bucketName,\n Key: `backups/${fileName}`,\n Body: fileBuffer,\n ContentType: 'application/zip',\n })\n\n await s3Client.send(command)\n console.log(`[notice-utility] Backup uploaded to S3: backups/${fileName}`)\n\n await logBackup({\n backupFileName: fileName,\n backupType: 'full',\n location: 's3',\n fileSize,\n status: 'success',\n })\n}\n\n/**\n * Write a backup log entry to the configured backupLogs collection.\n */\nasync function logBackup(entry: {\n backupFileName: string\n backupType: string\n location: 'local' | 's3'\n fileSize: number\n status: 'success' | 'failed'\n errorMessage?: string\n}): Promise<void> {\n try {\n const config = getConfig()\n const Model = getModel(config.tables.backupLogs)\n await Model.create({\n ...entry,\n createdAt: new Date(),\n })\n } catch (err) {\n console.error(\n '[notice-utility] Failed to write backup log:',\n err instanceof Error ? err.message : err\n )\n }\n}\n\n/**\n * Public export — trigger a manual database backup.\n */\nexport async function manualBackupTrigger(): Promise<void> {\n await performBackup()\n}\n"],"mappings":"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","names":["Model","Model"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/config.ts","../src/utils/db.ts","../src/utils/queue.ts","../src/exceptions/index.ts","../src/init.ts","../src/logger/index.ts","../src/backup/index.ts","../src/schedulers/backup.ts","../src/schedulers/cleanup.ts"],"sourcesContent":["import type { NoticeConfig } from '../types'\n\nlet _config: NoticeConfig | null = null\n\nexport function setConfig(config: NoticeConfig): void {\n _config = { ...config }\n}\n\nexport function getConfig(): NoticeConfig {\n if (!_config) {\n throw new Error(\n '[db-backup-logging] Package not initialized. Call initializeNoticePackage() first.'\n )\n }\n return _config\n}\n","import mongoose, { Schema, Model, Connection } from 'mongoose'\nimport { getConfig } from './config'\n\nconst modelCache = new Map<string, Model<any>>()\n\nlet localConnection: Connection | null = null\n\nexport function getDbConnection(): Connection {\n if (!localConnection) {\n localConnection = mongoose.createConnection(getConfig().dbUri)\n }\n return localConnection\n}\n\n/**\n * Gets or creates a Mongoose model for the given collection name.\n * Uses a flexible mixed schema to support any log structure.\n */\nexport function getModel(collectionName: string): Model<any> {\n if (modelCache.has(collectionName)) {\n return modelCache.get(collectionName)!\n }\n\n const schema = new Schema(\n {},\n {\n strict: false,\n timestamps: false,\n collection: collectionName,\n }\n )\n\n // Use a dedicated connection for db-backup-logging to avoid 'npm link' duplication issues\n const conn: Connection = getDbConnection()\n\n // Avoid OverwriteModelError — check if model already exists\n let model: Model<any>\n try {\n model = conn.model(collectionName)\n } catch {\n model = conn.model(collectionName, schema)\n }\n\n modelCache.set(collectionName, model)\n return model\n}\n\n/**\n * Creates a separate Mongoose connection for backup purposes (mongodump URI).\n */\nexport function getDbUri(): string {\n return getConfig().dbUri\n}\n","import { getModel } from './db'\n\ninterface QueueItem {\n collectionName: string\n data: Record<string, unknown>\n}\n\nconst queue: QueueItem[] = []\nlet isProcessing = false\nconst BATCH_SIZE = 50\nconst FLUSH_INTERVAL_MS = 5000\nconst MAX_RETRIES = 3\n\nlet flushTimer: ReturnType<typeof setInterval> | null = null\n\n/**\n * Enqueue a log entry for async writing to the database.\n * This never throws — all errors are silently logged to console.\n */\nexport function enqueue(collectionName: string, data: Record<string, unknown>): void {\n queue.push({ collectionName, data })\n\n // Start the flush interval if not already running\n if (!flushTimer) {\n flushTimer = setInterval(() => {\n flush().catch(() => {})\n }, FLUSH_INTERVAL_MS)\n\n // Unref so the timer doesn't keep the process alive\n if (flushTimer && typeof flushTimer === 'object' && 'unref' in flushTimer) {\n flushTimer.unref()\n }\n }\n\n // If queue reaches batch size, flush immediately\n if (queue.length >= BATCH_SIZE) {\n flush().catch(() => {})\n }\n}\n\n/**\n * Flush all queued log entries to the database.\n */\nexport async function flush(): Promise<void> {\n if (isProcessing || queue.length === 0) return\n isProcessing = true\n\n const batch = queue.splice(0, BATCH_SIZE)\n\n // Group by collection\n const grouped = new Map<string, Record<string, unknown>[]>()\n for (const item of batch) {\n const list = grouped.get(item.collectionName) || []\n list.push(item.data)\n grouped.set(item.collectionName, list)\n }\n\n for (const [collectionName, docs] of grouped) {\n let retries = 0\n while (retries < MAX_RETRIES) {\n try {\n const Model = getModel(collectionName)\n await Model.insertMany(docs, { ordered: false })\n break\n } catch (err) {\n retries++\n if (retries >= MAX_RETRIES) {\n console.error(\n `[db-backup-logging] Failed to write ${docs.length} logs to \"${collectionName}\" after ${MAX_RETRIES} retries:`,\n err instanceof Error ? err.message : err\n )\n } else {\n // Brief delay before retry\n await new Promise((r) => setTimeout(r, 100 * retries))\n }\n }\n }\n }\n\n isProcessing = false\n\n // If there are more items in the queue, continue flushing\n if (queue.length > 0) {\n flush().catch(() => {})\n }\n}\n","import type { Request, Response, NextFunction } from 'express'\nimport { getConfig } from '../utils/config'\nimport { enqueue } from '../utils/queue'\n\n/**\n * Log an error entry to the configured errorLogs collection.\n */\nfunction logError(\n error: Error | string,\n context?: {\n req?: Request\n userId?: string\n }\n): void {\n try {\n const config = getConfig()\n const err = typeof error === 'string' ? new Error(error) : error\n\n const logEntry: Record<string, unknown> = {\n errorMessage: err.message,\n stackTrace: err.stack || null,\n requestContext: context?.req\n ? {\n method: context.req.method,\n url: context.req.originalUrl || context.req.url,\n headers: context.req.headers,\n body: context.req.body || null,\n }\n : null,\n userId:\n context?.userId ||\n (context?.req as any)?.user?.id ||\n (context?.req as any)?.user?._id ||\n null,\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.errorLogs, logEntry)\n } catch {\n // Silent — error logging failure must never cause further errors\n }\n}\n\n/**\n * Register global process-level exception handlers.\n * Chains with existing handlers — does NOT override them.\n */\nexport function registerExceptionHandlers(): void {\n // Store references to any existing handlers so we can chain\n const existingUncaught = process.listeners('uncaughtException').slice()\n const existingRejection = process.listeners('unhandledRejection').slice()\n\n // uncaughtException\n process.on('uncaughtException', (error: Error) => {\n logError(error)\n // Don't prevent the default crash behavior for truly unrecoverable errors\n // The existing handlers will still fire since we're adding, not replacing\n })\n\n // unhandledRejection\n process.on('unhandledRejection', (reason: unknown) => {\n const error = reason instanceof Error ? reason : new Error(String(reason))\n logError(error)\n })\n}\n\n/**\n * Express error-handling middleware.\n * Must be registered AFTER all routes: app.use(errorMiddleware())\n */\nexport function errorMiddleware() {\n return (err: Error, req: Request, res: Response, next: NextFunction): void => {\n logError(err, { req })\n\n // Pass to the next error handler (don't swallow the error)\n next(err)\n }\n}\n\n/**\n * Public export — manually log a custom error with optional context.\n */\nexport function logCustomError(\n error: Error | string,\n context?: {\n userId?: string\n requestContext?: {\n method?: string\n url?: string\n headers?: Record<string, string | string[] | undefined>\n body?: unknown\n }\n }\n): void {\n try {\n const config = getConfig()\n const err = typeof error === 'string' ? new Error(error) : error\n\n const logEntry: Record<string, unknown> = {\n errorMessage: err.message,\n stackTrace: err.stack || null,\n requestContext: context?.requestContext || null,\n userId: context?.userId || null,\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.errorLogs, logEntry)\n } catch {\n // Silent\n }\n}\n","import type { NoticeConfig } from './types'\nimport { setConfig } from './utils/config'\nimport { registerExceptionHandlers } from './exceptions'\n\n/**\n * Initialize the db-backup-logging package.\n * Must be called once during application startup, after mongoose.connect().\n *\n * @param config - Full package configuration\n */\nexport function initializeNoticePackage(config: NoticeConfig): void {\n // Validate required fields\n if (!config.dbType) {\n throw new Error('[db-backup-logging] \"dbType\" is required in config.')\n }\n if (!config.dbUri) {\n throw new Error('[db-backup-logging] \"dbUri\" is required in config.')\n }\n if (!config.tables) {\n throw new Error('[db-backup-logging] \"tables\" configuration is required.')\n }\n if (!config.tables.requestLogs || !config.tables.errorLogs || !config.tables.backupLogs) {\n throw new Error(\n '[db-backup-logging] All table names (requestLogs, errorLogs, backupLogs) must be configured.'\n )\n }\n\n // Validate AWS config if enabled\n if (config.aws?.enabled) {\n if (!config.aws.bucketName || !config.aws.region) {\n throw new Error(\n '[db-backup-logging] AWS \"bucketName\" and \"region\" are required when AWS is enabled.'\n )\n }\n if (!config.aws.accessKeyId || !config.aws.secretAccessKey) {\n throw new Error(\n '[db-backup-logging] AWS \"accessKeyId\" and \"secretAccessKey\" are required when AWS is enabled.'\n )\n }\n\n try {\n require.resolve('@aws-sdk/client-s3')\n } catch {\n throw new Error(\n '[db-backup-logging] AWS backup is enabled in config, but \"@aws-sdk/client-s3\" is not installed. Please run: npm install @aws-sdk/client-s3'\n )\n }\n }\n\n // Validate local config if enabled\n if (config.local?.enabled) {\n if (!config.local.backupPath) {\n throw new Error(\n '[db-backup-logging] Local \"backupPath\" is required when local backup is enabled.'\n )\n }\n }\n\n // Store config\n setConfig(config)\n\n // Register global exception handlers\n registerExceptionHandlers()\n\n console.log(\n `[db-backup-logging] Initialized successfully (db: ${config.dbType}, service: ${config.serviceName || 'default'}, env: ${config.environment || process.env.NODE_ENV || 'development'})`\n )\n}\n","import type { Request, Response, NextFunction } from 'express'\nimport { getConfig } from '../utils/config'\nimport { enqueue } from '../utils/queue'\n\n/**\n * Express middleware factory for request/response logging.\n * Captures method, URL, headers, body, response, timing, user, IP.\n * All logging is non-blocking via the async queue.\n */\nexport function requestLogger() {\n return (req: Request, res: Response, next: NextFunction): void => {\n const startTime = Date.now()\n\n // Capture the original res.json to intercept the response body\n const originalJson = res.json.bind(res)\n let responseBody: unknown = undefined\n\n res.json = function (body: any) {\n responseBody = body\n return originalJson(body)\n }\n\n // Hook into the 'finish' event to log after response is sent\n res.on('finish', () => {\n try {\n const config = getConfig()\n const responseTime = Date.now() - startTime\n\n const logEntry: Record<string, unknown> = {\n method: req.method,\n url: req.originalUrl || req.url,\n headers: req.headers,\n requestBody: req.body || null,\n responseStatus: res.statusCode,\n responseBody: responseBody ?? null,\n responseTime,\n userId: (req as any).user?.id || (req as any).user?._id || (req as any).userId || null,\n ipAddress:\n (req.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() ||\n req.socket?.remoteAddress ||\n 'unknown',\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.requestLogs, logEntry)\n } catch {\n // Silent — logging failure must never affect the request\n }\n })\n\n next()\n }\n}\n","import { exec } from 'child_process'\nimport { promisify } from 'util'\nimport * as path from 'path'\nimport * as fs from 'fs'\nimport { getConfig } from '../utils/config'\nimport { getModel, getDbUri } from '../utils/db'\n\nconst execAsync = promisify(exec)\n\n/**\n * Generate a timestamp-based backup file name.\n */\nfunction getBackupFileName(): string {\n const now = new Date()\n const ts = now.toISOString().replace(/[:.]/g, '-')\n return `backup-${ts}`\n}\n\n/**\n * Perform a full MongoDB backup using mongodump.\n * Saves to local filesystem and/or uploads to S3 based on config.\n */\nasync function performBackup(): Promise<void> {\n const config = getConfig()\n const dbUri = getDbUri()\n const fileName = getBackupFileName()\n const archiveName = `${fileName}.zip`\n\n // Temp directory for backup\n const tempDir = path.resolve(process.cwd(), '.notice-backups-tmp')\n if (!fs.existsSync(tempDir)) {\n fs.mkdirSync(tempDir, { recursive: true })\n }\n\n const dumpDir = path.join(tempDir, fileName)\n const archivePath = path.join(tempDir, archiveName)\n\n try {\n // Run mongodump to a directory\n console.log(`[db-backup-logging] Starting backup: ${archiveName}`)\n await execAsync(`mongodump --uri=\"${dbUri}\" --out=\"${dumpDir}\"`)\n\n // Zip the directory\n await execAsync(`zip -r \"${archivePath}\" \"${fileName}\"`, { cwd: tempDir })\n\n const stats = fs.statSync(archivePath)\n const fileSize = stats.size\n\n // ── Save to local storage ──\n if (config.local?.enabled) {\n const localDir = path.resolve(process.cwd(), config.local.backupPath)\n if (!fs.existsSync(localDir)) {\n fs.mkdirSync(localDir, { recursive: true })\n }\n const localPath = path.join(localDir, archiveName)\n fs.copyFileSync(archivePath, localPath)\n console.log(`[db-backup-logging] Backup saved locally: ${localPath}`)\n\n // Log to database\n await logBackup({\n backupFileName: archiveName,\n backupType: 'full',\n location: 'local',\n fileSize,\n status: 'success',\n })\n }\n\n // ── Upload to S3 ──\n if (config.aws?.enabled) {\n await uploadToS3(archivePath, archiveName, fileSize)\n }\n\n // Cleanup temp files\n if (fs.existsSync(archivePath)) {\n fs.unlinkSync(archivePath)\n }\n if (fs.existsSync(dumpDir)) {\n fs.rmSync(dumpDir, { recursive: true, force: true })\n }\n\n console.log(`[db-backup-logging] Backup completed: ${archiveName}`)\n } catch (err) {\n console.error('[db-backup-logging] Backup failed:', err instanceof Error ? err.message : err)\n\n await logBackup({\n backupFileName: archiveName,\n backupType: 'full',\n location: config.aws?.enabled ? 's3' : 'local',\n fileSize: 0,\n status: 'failed',\n errorMessage: err instanceof Error ? err.message : String(err),\n }).catch(() => {})\n\n // Don't rethrow — backup failure should not crash the host app\n }\n}\n\n/**\n * Upload backup archive to AWS S3.\n */\nasync function uploadToS3(filePath: string, fileName: string, fileSize: number): Promise<void> {\n const config = getConfig()\n if (!config.aws) throw new Error('AWS config not provided')\n\n const { S3Client, PutObjectCommand } = await import('@aws-sdk/client-s3')\n\n const s3Client = new S3Client({\n region: config.aws.region,\n credentials: {\n accessKeyId: config.aws.accessKeyId,\n secretAccessKey: config.aws.secretAccessKey,\n },\n })\n\n const fileBuffer = fs.readFileSync(filePath)\n\n const command = new PutObjectCommand({\n Bucket: config.aws.bucketName,\n Key: `backups/${fileName}`,\n Body: fileBuffer,\n ContentType: 'application/zip',\n })\n\n await s3Client.send(command)\n console.log(`[db-backup-logging] Backup uploaded to S3: backups/${fileName}`)\n\n await logBackup({\n backupFileName: fileName,\n backupType: 'full',\n location: 's3',\n fileSize,\n status: 'success',\n })\n}\n\n/**\n * Write a backup log entry to the configured backupLogs collection.\n */\nasync function logBackup(entry: {\n backupFileName: string\n backupType: string\n location: 'local' | 's3'\n fileSize: number\n status: 'success' | 'failed'\n errorMessage?: string\n}): Promise<void> {\n try {\n const config = getConfig()\n const Model = getModel(config.tables.backupLogs)\n await Model.create({\n ...entry,\n createdAt: new Date(),\n })\n } catch (err) {\n console.error(\n '[db-backup-logging] Failed to write backup log:',\n err instanceof Error ? err.message : err\n )\n }\n}\n\n/**\n * Public export — trigger a manual database backup.\n */\nexport async function manualBackupTrigger(): Promise<void> {\n await performBackup()\n}\n","import { getConfig } from '../utils/config'\nimport { manualBackupTrigger } from '../backup'\n\n/**\n * Dynamically resolve node-cron from the host project.\n * Throws a helpful error if not installed.\n */\nfunction loadNodeCron(): typeof import('node-cron') {\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n return require('node-cron')\n } catch {\n throw new Error(\n '[db-backup-logging] node-cron is required to use initBackupScheduler().\\n' +\n 'Please install it in your root project:\\n' +\n ' npm install node-cron\\n' +\n ' yarn add node-cron'\n )\n }\n}\n\n/**\n * Start an automatic backup cron job.\n *\n * Schedule and timezone are resolved in order:\n * 1. Environment variables BACKUP_CRON_SCHEDULE / BACKUP_TIMEZONE\n * 2. config.backupCron passed to initializeNoticePackage()\n * 3. Built-in defaults (\"0 2 * * *\" = 2 AM daily, UTC)\n *\n * Requires node-cron >= 3.0.0 installed in the host project.\n */\nexport function initBackupScheduler(): void {\n const cron = loadNodeCron()\n const config = getConfig()\n\n const schedule =\n process.env.BACKUP_CRON_SCHEDULE ||\n config.backupCron?.schedule ||\n '0 2 * * *'\n\n const timezone =\n process.env.BACKUP_TIMEZONE ||\n config.backupCron?.timezone ||\n 'UTC'\n\n if (!cron.validate(schedule)) {\n throw new Error(\n `[db-backup-logging] Invalid BACKUP_CRON_SCHEDULE: \"${schedule}\". ` +\n 'Please provide a valid cron expression.'\n )\n }\n\n cron.schedule(\n schedule,\n async () => {\n console.log('[db-backup-logging] Backup scheduler triggered.')\n await manualBackupTrigger()\n },\n { timezone }\n )\n\n console.log(\n `[db-backup-logging] Backup scheduler started — schedule: \"${schedule}\", timezone: \"${timezone}\"`\n )\n}\n","import { getConfig } from '../utils/config'\nimport { getModel } from '../utils/db'\n\n/**\n * Dynamically resolve node-cron from the host project.\n * Throws a helpful error if not installed.\n */\nfunction loadNodeCron(): typeof import('node-cron') {\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n return require('node-cron')\n } catch {\n throw new Error(\n '[db-backup-logging] node-cron is required to use initLogCleanupScheduler().\\n' +\n 'Please install it in your root project:\\n' +\n ' npm install node-cron\\n' +\n ' yarn add node-cron'\n )\n }\n}\n\n/**\n * Delete request/error log documents older than retentionDays.\n */\nasync function purgeOldLogs(retentionDays: number): Promise<void> {\n const config = getConfig()\n const cutoff = new Date()\n cutoff.setDate(cutoff.getDate() - retentionDays)\n\n const collections = [config.tables.requestLogs, config.tables.errorLogs]\n\n for (const collectionName of collections) {\n try {\n const Model = getModel(collectionName)\n const result = await Model.deleteMany({ createdAt: { $lt: cutoff } })\n console.log(\n `[db-backup-logging] Log cleanup: removed ${result.deletedCount} record(s) from \"${collectionName}\" older than ${retentionDays} day(s).`\n )\n } catch (err) {\n console.error(\n `[db-backup-logging] Log cleanup failed for \"${collectionName}\":`,\n err instanceof Error ? err.message : err\n )\n }\n }\n}\n\n/**\n * Start an automatic log cleanup cron job.\n *\n * Schedule, timezone and retention days are resolved in order:\n * 1. Environment variables LOG_CLEANUP_CRON_SCHEDULE / LOG_CLEANUP_TIMEZONE / LOG_RETENTION_DAYS\n * 2. config.logCleanupCron passed to initializeNoticePackage()\n * 3. Built-in defaults (\"0 3 * * *\" = 3 AM daily, UTC, 30 days retention)\n *\n * Requires node-cron >= 3.0.0 installed in the host project.\n */\nexport function initLogCleanupScheduler(): void {\n const cron = loadNodeCron()\n const config = getConfig()\n\n const schedule =\n process.env.LOG_CLEANUP_CRON_SCHEDULE ||\n config.logCleanupCron?.schedule ||\n '0 3 * * *'\n\n const timezone =\n process.env.LOG_CLEANUP_TIMEZONE ||\n config.logCleanupCron?.timezone ||\n 'UTC'\n\n const retentionDays =\n Number(process.env.LOG_RETENTION_DAYS) ||\n config.logCleanupCron?.retentionDays ||\n 30\n\n if (!cron.validate(schedule)) {\n throw new Error(\n `[db-backup-logging] Invalid LOG_CLEANUP_CRON_SCHEDULE: \"${schedule}\". ` +\n 'Please provide a valid cron expression.'\n )\n }\n\n cron.schedule(\n schedule,\n async () => {\n console.log('[db-backup-logging] Log cleanup scheduler triggered.')\n await purgeOldLogs(retentionDays)\n },\n { timezone }\n )\n\n console.log(\n `[db-backup-logging] Log cleanup scheduler started — schedule: \"${schedule}\", timezone: \"${timezone}\", retentionDays: ${retentionDays}`\n )\n}\n"],"mappings":"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","names":["Model","Model","loadNodeCron","Model"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "db-backup-logging",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Production-ready NPM package for database backup, request/response logging, and exception tracking",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -32,8 +32,10 @@
|
|
|
32
32
|
"author": "",
|
|
33
33
|
"license": "ISC",
|
|
34
34
|
"peerDependencies": {
|
|
35
|
+
"@aws-sdk/client-s3": "^3.490.0",
|
|
36
|
+
"express": ">=4.0.0",
|
|
35
37
|
"mongoose": ">=6.0.0",
|
|
36
|
-
"
|
|
38
|
+
"node-cron": ">=3.0.0"
|
|
37
39
|
},
|
|
38
40
|
"peerDependenciesMeta": {
|
|
39
41
|
"mongoose": {
|
|
@@ -41,20 +43,24 @@
|
|
|
41
43
|
},
|
|
42
44
|
"express": {
|
|
43
45
|
"optional": false
|
|
46
|
+
},
|
|
47
|
+
"@aws-sdk/client-s3": {
|
|
48
|
+
"optional": true
|
|
49
|
+
},
|
|
50
|
+
"node-cron": {
|
|
51
|
+
"optional": true
|
|
44
52
|
}
|
|
45
53
|
},
|
|
46
54
|
"devDependencies": {
|
|
47
55
|
"@types/express": "^4.17.21",
|
|
48
56
|
"@types/node": "^20.11.0",
|
|
57
|
+
"@types/node-cron": "^3.0.11",
|
|
49
58
|
"express": "^4.18.2",
|
|
50
59
|
"mongoose": "^8.1.0",
|
|
51
60
|
"tsup": "^8.0.1",
|
|
52
61
|
"typescript": "^5.3.3"
|
|
53
62
|
},
|
|
54
|
-
"dependencies": {
|
|
55
|
-
"@aws-sdk/client-s3": "^3.490.0"
|
|
56
|
-
},
|
|
57
63
|
"engines": {
|
|
58
64
|
"node": ">=16.0.0"
|
|
59
65
|
}
|
|
60
|
-
}
|
|
66
|
+
}
|