db-backup-logging 1.0.0 → 1.0.2
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 +8 -110
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +23 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +30 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# db-backup-logging
|
|
2
2
|
|
|
3
3
|
Production-ready NPM package for **database backup**, **request/response logging**, and **exception tracking**.
|
|
4
4
|
Written in TypeScript. Supports CommonJS + ESM.
|
|
@@ -8,7 +8,7 @@ Written in TypeScript. Supports CommonJS + ESM.
|
|
|
8
8
|
## Installation
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
npm install
|
|
11
|
+
npm install db-backup-logging
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
**Peer dependencies** (must exist in your project):
|
|
@@ -28,7 +28,7 @@ npm install @aws-sdk/client-s3
|
|
|
28
28
|
### 1. Initialize (after `mongoose.connect()`)
|
|
29
29
|
|
|
30
30
|
```ts
|
|
31
|
-
import { initializeNoticePackage } from '
|
|
31
|
+
import { initializeNoticePackage } from 'db-backup-logging'
|
|
32
32
|
|
|
33
33
|
initializeNoticePackage({
|
|
34
34
|
dbType: 'mongodb',
|
|
@@ -59,7 +59,7 @@ initializeNoticePackage({
|
|
|
59
59
|
### 2. Add Request Logger Middleware
|
|
60
60
|
|
|
61
61
|
```ts
|
|
62
|
-
import { requestLogger } from '
|
|
62
|
+
import { requestLogger } from 'db-backup-logging'
|
|
63
63
|
|
|
64
64
|
// Add BEFORE your routes
|
|
65
65
|
app.use(requestLogger())
|
|
@@ -70,7 +70,7 @@ Logs: HTTP method, URL, headers, request body, response status, response body, r
|
|
|
70
70
|
### 3. Add Error Middleware
|
|
71
71
|
|
|
72
72
|
```ts
|
|
73
|
-
import { errorMiddleware } from '
|
|
73
|
+
import { errorMiddleware } from 'db-backup-logging'
|
|
74
74
|
|
|
75
75
|
// Add AFTER all routes
|
|
76
76
|
app.use(errorMiddleware())
|
|
@@ -81,7 +81,7 @@ This also automatically registers `uncaughtException` and `unhandledRejection` h
|
|
|
81
81
|
### 4. Trigger Manual Backup
|
|
82
82
|
|
|
83
83
|
```ts
|
|
84
|
-
import { manualBackupTrigger } from '
|
|
84
|
+
import { manualBackupTrigger } from 'db-backup-logging'
|
|
85
85
|
|
|
86
86
|
await manualBackupTrigger()
|
|
87
87
|
```
|
|
@@ -91,7 +91,7 @@ await manualBackupTrigger()
|
|
|
91
91
|
### 5. Log Custom Errors
|
|
92
92
|
|
|
93
93
|
```ts
|
|
94
|
-
import { logCustomError } from '
|
|
94
|
+
import { logCustomError } from 'db-backup-logging'
|
|
95
95
|
|
|
96
96
|
logCustomError(new Error('Payment failed'), {
|
|
97
97
|
userId: 'user_123',
|
|
@@ -147,111 +147,9 @@ interface NoticeConfig {
|
|
|
147
147
|
All types are also exported: `NoticeConfig`, `AWSConfig`, `LocalConfig`, `NoticeTables`, `RequestLogEntry`, `ErrorLogEntry`, `BackupLogEntry`.
|
|
148
148
|
|
|
149
149
|
---
|
|
150
|
-
|
|
151
|
-
## Testing Locally (Before Publishing)
|
|
152
|
-
|
|
153
|
-
### Step 1: Build the package
|
|
154
|
-
|
|
155
|
-
```bash
|
|
156
|
-
cd /path/to/db-backup-logging
|
|
157
|
-
npm install
|
|
158
|
-
npm run build
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
Verify `dist/` contains: `index.js`, `index.mjs`, `index.d.ts`.
|
|
162
|
-
|
|
163
|
-
### Step 2: Link the package locally
|
|
164
|
-
|
|
165
|
-
```bash
|
|
166
|
-
cd /path/to/db-backup-logging
|
|
167
|
-
npm link
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### Step 3: Use in your project
|
|
171
|
-
|
|
172
|
-
```bash
|
|
173
|
-
cd /path/to/your-project
|
|
174
|
-
npm link notice-utility
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
Now you can import it:
|
|
178
|
-
|
|
179
|
-
```ts
|
|
180
|
-
import { initializeNoticePackage, requestLogger } from 'notice-utility'
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
### Step 4: Verify it works
|
|
184
|
-
|
|
185
|
-
Add this to your Express app's entry file (e.g. `server.js` or `app.ts`):
|
|
186
|
-
|
|
187
|
-
```ts
|
|
188
|
-
import mongoose from 'mongoose'
|
|
189
|
-
import express from 'express'
|
|
190
|
-
import {
|
|
191
|
-
initializeNoticePackage,
|
|
192
|
-
requestLogger,
|
|
193
|
-
errorMiddleware,
|
|
194
|
-
} from 'notice-utility'
|
|
195
|
-
|
|
196
|
-
const app = express()
|
|
197
|
-
app.use(express.json())
|
|
198
|
-
|
|
199
|
-
// Connect to MongoDB first
|
|
200
|
-
await mongoose.connect('mongodb://localhost:27017/test_notice')
|
|
201
|
-
|
|
202
|
-
// Initialize the package
|
|
203
|
-
initializeNoticePackage({
|
|
204
|
-
dbType: 'mongodb',
|
|
205
|
-
dbUri: 'mongodb://localhost:27017/test_notice',
|
|
206
|
-
serviceName: 'test-app',
|
|
207
|
-
environment: 'development',
|
|
208
|
-
tables: {
|
|
209
|
-
requestLogs: 'test_request_logs',
|
|
210
|
-
errorLogs: 'test_error_logs',
|
|
211
|
-
backupLogs: 'test_backup_logs',
|
|
212
|
-
},
|
|
213
|
-
local: { enabled: true, backupPath: './test-backups' },
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
// Add middleware
|
|
217
|
-
app.use(requestLogger())
|
|
218
|
-
|
|
219
|
-
app.get('/test', (req, res) => {
|
|
220
|
-
res.json({ message: 'Hello from notice-utility test!' })
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
app.use(errorMiddleware())
|
|
224
|
-
|
|
225
|
-
app.listen(3000, () => console.log('Test server running on port 3000'))
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### Step 5: Test endpoints
|
|
229
|
-
|
|
230
|
-
```bash
|
|
231
|
-
# Hit the test endpoint
|
|
232
|
-
curl http://localhost:3000/test
|
|
233
|
-
|
|
234
|
-
# Check MongoDB for logged request
|
|
235
|
-
mongosh test_notice --eval "db.test_request_logs.find().pretty()"
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
You should see a request log entry with method, URL, response status, response time, etc.
|
|
239
|
-
|
|
240
|
-
### Step 6: Unlink when done
|
|
241
|
-
|
|
242
|
-
```bash
|
|
243
|
-
cd /path/to/your-project
|
|
244
|
-
npm unlink notice-utility
|
|
245
|
-
|
|
246
|
-
cd /path/to/db-backup-logging
|
|
247
|
-
npm unlink
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
---
|
|
251
|
-
|
|
252
150
|
## Notes
|
|
253
151
|
|
|
254
|
-
- **Dedicated Connection**: `
|
|
152
|
+
- **Dedicated Connection**: `db-backup-logging` establishes its own dedicated database connection using `config.dbUri` to ensure logging operations never compete with your main application's query pool (and to avoid `npm link` Mongoose duplication issues).
|
|
255
153
|
- Logging failures are **silently handled** — the host app will never crash due to a logging error.
|
|
256
154
|
- Exception handlers **chain** with existing handlers — they don't override them.
|
|
257
155
|
- `mongodump` must be installed on the machine for the backup feature to work.
|
package/dist/index.d.mts
CHANGED
|
@@ -65,7 +65,7 @@ interface BackupLogEntry {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
* Initialize the
|
|
68
|
+
* Initialize the db-backup-logging package.
|
|
69
69
|
* Must be called once during application startup, after mongoose.connect().
|
|
70
70
|
*
|
|
71
71
|
* @param config - Full package configuration
|
package/dist/index.d.ts
CHANGED
|
@@ -65,7 +65,7 @@ interface BackupLogEntry {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
* Initialize the
|
|
68
|
+
* Initialize the db-backup-logging package.
|
|
69
69
|
* Must be called once during application startup, after mongoose.connect().
|
|
70
70
|
*
|
|
71
71
|
* @param config - Full package configuration
|
package/dist/index.js
CHANGED
|
@@ -46,7 +46,7 @@ function setConfig(config) {
|
|
|
46
46
|
function getConfig() {
|
|
47
47
|
if (!_config) {
|
|
48
48
|
throw new Error(
|
|
49
|
-
"[
|
|
49
|
+
"[db-backup-logging] Package not initialized. Call initializeNoticePackage() first."
|
|
50
50
|
);
|
|
51
51
|
}
|
|
52
52
|
return _config;
|
|
@@ -132,7 +132,7 @@ async function flush() {
|
|
|
132
132
|
retries++;
|
|
133
133
|
if (retries >= MAX_RETRIES) {
|
|
134
134
|
console.error(
|
|
135
|
-
`[
|
|
135
|
+
`[db-backup-logging] Failed to write ${docs.length} logs to "${collectionName}" after ${MAX_RETRIES} retries:`,
|
|
136
136
|
err instanceof Error ? err.message : err
|
|
137
137
|
);
|
|
138
138
|
} else {
|
|
@@ -209,42 +209,49 @@ function logCustomError(error, context) {
|
|
|
209
209
|
// src/init.ts
|
|
210
210
|
function initializeNoticePackage(config) {
|
|
211
211
|
if (!config.dbType) {
|
|
212
|
-
throw new Error('[
|
|
212
|
+
throw new Error('[db-backup-logging] "dbType" is required in config.');
|
|
213
213
|
}
|
|
214
214
|
if (!config.dbUri) {
|
|
215
|
-
throw new Error('[
|
|
215
|
+
throw new Error('[db-backup-logging] "dbUri" is required in config.');
|
|
216
216
|
}
|
|
217
217
|
if (!config.tables) {
|
|
218
|
-
throw new Error('[
|
|
218
|
+
throw new Error('[db-backup-logging] "tables" configuration is required.');
|
|
219
219
|
}
|
|
220
220
|
if (!config.tables.requestLogs || !config.tables.errorLogs || !config.tables.backupLogs) {
|
|
221
221
|
throw new Error(
|
|
222
|
-
"[
|
|
222
|
+
"[db-backup-logging] All table names (requestLogs, errorLogs, backupLogs) must be configured."
|
|
223
223
|
);
|
|
224
224
|
}
|
|
225
225
|
if (config.aws?.enabled) {
|
|
226
226
|
if (!config.aws.bucketName || !config.aws.region) {
|
|
227
227
|
throw new Error(
|
|
228
|
-
'[
|
|
228
|
+
'[db-backup-logging] AWS "bucketName" and "region" are required when AWS is enabled.'
|
|
229
229
|
);
|
|
230
230
|
}
|
|
231
231
|
if (!config.aws.accessKeyId || !config.aws.secretAccessKey) {
|
|
232
232
|
throw new Error(
|
|
233
|
-
'[
|
|
233
|
+
'[db-backup-logging] AWS "accessKeyId" and "secretAccessKey" are required when AWS is enabled.'
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
require.resolve("@aws-sdk/client-s3");
|
|
238
|
+
} catch {
|
|
239
|
+
throw new Error(
|
|
240
|
+
'[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
241
|
);
|
|
235
242
|
}
|
|
236
243
|
}
|
|
237
244
|
if (config.local?.enabled) {
|
|
238
245
|
if (!config.local.backupPath) {
|
|
239
246
|
throw new Error(
|
|
240
|
-
'[
|
|
247
|
+
'[db-backup-logging] Local "backupPath" is required when local backup is enabled.'
|
|
241
248
|
);
|
|
242
249
|
}
|
|
243
250
|
}
|
|
244
251
|
setConfig(config);
|
|
245
252
|
registerExceptionHandlers();
|
|
246
253
|
console.log(
|
|
247
|
-
`[
|
|
254
|
+
`[db-backup-logging] Initialized successfully (db: ${config.dbType}, service: ${config.serviceName || "default"}, env: ${config.environment || process.env.NODE_ENV || "development"})`
|
|
248
255
|
);
|
|
249
256
|
}
|
|
250
257
|
|
|
@@ -307,7 +314,7 @@ async function performBackup() {
|
|
|
307
314
|
const dumpDir = path.join(tempDir, fileName);
|
|
308
315
|
const archivePath = path.join(tempDir, archiveName);
|
|
309
316
|
try {
|
|
310
|
-
console.log(`[
|
|
317
|
+
console.log(`[db-backup-logging] Starting backup: ${archiveName}`);
|
|
311
318
|
await execAsync(`mongodump --uri="${dbUri}" --out="${dumpDir}"`);
|
|
312
319
|
await execAsync(`zip -r "${archivePath}" "${fileName}"`, { cwd: tempDir });
|
|
313
320
|
const stats = fs.statSync(archivePath);
|
|
@@ -319,7 +326,7 @@ async function performBackup() {
|
|
|
319
326
|
}
|
|
320
327
|
const localPath = path.join(localDir, archiveName);
|
|
321
328
|
fs.copyFileSync(archivePath, localPath);
|
|
322
|
-
console.log(`[
|
|
329
|
+
console.log(`[db-backup-logging] Backup saved locally: ${localPath}`);
|
|
323
330
|
await logBackup({
|
|
324
331
|
backupFileName: archiveName,
|
|
325
332
|
backupType: "full",
|
|
@@ -337,9 +344,9 @@ async function performBackup() {
|
|
|
337
344
|
if (fs.existsSync(dumpDir)) {
|
|
338
345
|
fs.rmSync(dumpDir, { recursive: true, force: true });
|
|
339
346
|
}
|
|
340
|
-
console.log(`[
|
|
347
|
+
console.log(`[db-backup-logging] Backup completed: ${archiveName}`);
|
|
341
348
|
} catch (err) {
|
|
342
|
-
console.error("[
|
|
349
|
+
console.error("[db-backup-logging] Backup failed:", err instanceof Error ? err.message : err);
|
|
343
350
|
await logBackup({
|
|
344
351
|
backupFileName: archiveName,
|
|
345
352
|
backupType: "full",
|
|
@@ -370,7 +377,7 @@ async function uploadToS3(filePath, fileName, fileSize) {
|
|
|
370
377
|
ContentType: "application/zip"
|
|
371
378
|
});
|
|
372
379
|
await s3Client.send(command);
|
|
373
|
-
console.log(`[
|
|
380
|
+
console.log(`[db-backup-logging] Backup uploaded to S3: backups/${fileName}`);
|
|
374
381
|
await logBackup({
|
|
375
382
|
backupFileName: fileName,
|
|
376
383
|
backupType: "full",
|
|
@@ -389,7 +396,7 @@ async function logBackup(entry) {
|
|
|
389
396
|
});
|
|
390
397
|
} catch (err) {
|
|
391
398
|
console.error(
|
|
392
|
-
"[
|
|
399
|
+
"[db-backup-logging] Failed to write backup log:",
|
|
393
400
|
err instanceof Error ? err.message : err
|
|
394
401
|
);
|
|
395
402
|
}
|
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"],"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 '[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"],"mappings":"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","names":["mongoose","Model","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
|
}
|
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"],"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"],"mappings":"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","names":["Model","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.2",
|
|
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",
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"license": "ISC",
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"mongoose": ">=6.0.0",
|
|
36
|
-
"express": ">=4.0.0"
|
|
36
|
+
"express": ">=4.0.0",
|
|
37
|
+
"@aws-sdk/client-s3": "^3.490.0"
|
|
37
38
|
},
|
|
38
39
|
"peerDependenciesMeta": {
|
|
39
40
|
"mongoose": {
|
|
@@ -41,6 +42,9 @@
|
|
|
41
42
|
},
|
|
42
43
|
"express": {
|
|
43
44
|
"optional": false
|
|
45
|
+
},
|
|
46
|
+
"@aws-sdk/client-s3": {
|
|
47
|
+
"optional": true
|
|
44
48
|
}
|
|
45
49
|
},
|
|
46
50
|
"devDependencies": {
|
|
@@ -49,12 +53,10 @@
|
|
|
49
53
|
"express": "^4.18.2",
|
|
50
54
|
"mongoose": "^8.1.0",
|
|
51
55
|
"tsup": "^8.0.1",
|
|
52
|
-
"typescript": "^5.3.3"
|
|
53
|
-
},
|
|
54
|
-
"dependencies": {
|
|
56
|
+
"typescript": "^5.3.3",
|
|
55
57
|
"@aws-sdk/client-s3": "^3.490.0"
|
|
56
58
|
},
|
|
57
59
|
"engines": {
|
|
58
60
|
"node": ">=16.0.0"
|
|
59
61
|
}
|
|
60
|
-
}
|
|
62
|
+
}
|