90dc-core 1.16.15 → 1.16.17
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/dist/lib/classes/Database.d.ts +2 -0
- package/dist/lib/classes/Database.d.ts.map +1 -1
- package/dist/lib/classes/Database.js +80 -8
- package/dist/lib/classes/Database.js.map +1 -1
- package/dist/lib/middlewares/ErrorMiddleware.d.ts.map +1 -1
- package/dist/lib/middlewares/ErrorMiddleware.js +25 -10
- package/dist/lib/middlewares/ErrorMiddleware.js.map +1 -1
- package/dist/lib/utils/SentryUtil.js +1 -1
- package/dist/lib/utils/SentryUtil.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ export interface DatabaseConfig {
|
|
|
5
5
|
DB_POOL_MIN?: number;
|
|
6
6
|
DB_ACQUIRE_TIMEOUT?: number;
|
|
7
7
|
DB_IDLE_TIMEOUT?: number;
|
|
8
|
+
DB_EVICT_INTERVAL?: number;
|
|
8
9
|
logging?: boolean;
|
|
9
10
|
}
|
|
10
11
|
export declare class DatabaseClient {
|
|
@@ -19,5 +20,6 @@ export declare class DatabaseClient {
|
|
|
19
20
|
}): Promise<void>;
|
|
20
21
|
static isConnectedToDatabase(): boolean;
|
|
21
22
|
static getSequelizeInstance(): Sequelize;
|
|
23
|
+
private static setupPoolMonitoring;
|
|
22
24
|
}
|
|
23
25
|
//# sourceMappingURL=Database.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Database.d.ts","sourceRoot":"","sources":["../../../src/lib/classes/Database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAyB,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAKxF,MAAM,WAAW,cAAc;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,qBAAa,cAAc;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA0B;IACjD,OAAO,CAAC,MAAM,CAAC,WAAW,CAAkB;IAE5C,MAAM,CAAC,WAAW,CACd,MAAM,CAAC,EAAE,cAAc,EACvB,MAAM,CAAC,EAAE,SAAS,EAAE,GACrB,SAAS;
|
|
1
|
+
{"version":3,"file":"Database.d.ts","sourceRoot":"","sources":["../../../src/lib/classes/Database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAyB,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAKxF,MAAM,WAAW,cAAc;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,qBAAa,cAAc;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA0B;IACjD,OAAO,CAAC,MAAM,CAAC,WAAW,CAAkB;IAE5C,MAAM,CAAC,WAAW,CACd,MAAM,CAAC,EAAE,cAAc,EACvB,MAAM,CAAC,EAAE,SAAS,EAAE,GACrB,SAAS;WA4CC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;WA2C7B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;WAStB,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBhF,MAAM,CAAC,qBAAqB,IAAI,OAAO;IAKvC,MAAM,CAAC,oBAAoB,IAAI,SAAS;IASxC,OAAO,CAAC,MAAM,CAAC,mBAAmB;CAkDrC"}
|
|
@@ -9,17 +9,19 @@ export class DatabaseClient {
|
|
|
9
9
|
if (!config) {
|
|
10
10
|
throw new Error("DatabaseClient: config is required for first initialization");
|
|
11
11
|
}
|
|
12
|
+
const poolConfig = {
|
|
13
|
+
max: config.DB_POOL_MAX || 10,
|
|
14
|
+
min: config.DB_POOL_MIN || 0,
|
|
15
|
+
acquire: config.DB_ACQUIRE_TIMEOUT || 60000,
|
|
16
|
+
idle: config.DB_IDLE_TIMEOUT || 10000,
|
|
17
|
+
evict: config.DB_EVICT_INTERVAL || 5000
|
|
18
|
+
};
|
|
12
19
|
const options = {
|
|
13
20
|
dialect: "postgres",
|
|
14
21
|
dialectOptions: {
|
|
15
22
|
socketPath: new URL(config.DATABASE_URL).hostname
|
|
16
23
|
},
|
|
17
|
-
pool:
|
|
18
|
-
max: config.DB_POOL_MAX || 10,
|
|
19
|
-
min: config.DB_POOL_MIN || 0,
|
|
20
|
-
acquire: config.DB_ACQUIRE_TIMEOUT || 60000,
|
|
21
|
-
idle: config.DB_IDLE_TIMEOUT || 10000
|
|
22
|
-
},
|
|
24
|
+
pool: poolConfig,
|
|
23
25
|
logging: config.logging !== false ? (msg)=>logger.debug(msg) : false,
|
|
24
26
|
models: models || [],
|
|
25
27
|
define: {
|
|
@@ -27,7 +29,11 @@ export class DatabaseClient {
|
|
|
27
29
|
}
|
|
28
30
|
};
|
|
29
31
|
DatabaseClient.instance = new Sequelize(config.DATABASE_URL, options);
|
|
30
|
-
logger.info("DatabaseClient: Sequelize instance created"
|
|
32
|
+
logger.info("DatabaseClient: Sequelize instance created", {
|
|
33
|
+
poolConfig
|
|
34
|
+
});
|
|
35
|
+
// Set up connection pool monitoring
|
|
36
|
+
DatabaseClient.setupPoolMonitoring();
|
|
31
37
|
} else if (models && models.length > 0) {
|
|
32
38
|
DatabaseClient.instance.addModels(models);
|
|
33
39
|
logger.info(`DatabaseClient: Added ${models.length} additional models`);
|
|
@@ -44,7 +50,29 @@ export class DatabaseClient {
|
|
|
44
50
|
logger.info("DatabaseClient: Connection authenticated successfully");
|
|
45
51
|
} catch (error) {
|
|
46
52
|
DatabaseClient.isConnected = false;
|
|
47
|
-
|
|
53
|
+
// Enhanced error handling for common connection issues
|
|
54
|
+
if (error instanceof Error) {
|
|
55
|
+
if (error.message.includes("too many clients")) {
|
|
56
|
+
logger.error("DatabaseClient: PostgreSQL connection pool exhausted", {
|
|
57
|
+
error: error.message,
|
|
58
|
+
recommendation: "Increase DB_POOL_MAX or check for connection leaks"
|
|
59
|
+
});
|
|
60
|
+
} else if (error.message.includes("timeout")) {
|
|
61
|
+
logger.error("DatabaseClient: Connection timeout", {
|
|
62
|
+
error: error.message,
|
|
63
|
+
recommendation: "Check network connectivity or increase DB_ACQUIRE_TIMEOUT"
|
|
64
|
+
});
|
|
65
|
+
} else if (error.message.includes("authentication failed")) {
|
|
66
|
+
logger.error("DatabaseClient: Authentication failed", {
|
|
67
|
+
error: error.message,
|
|
68
|
+
recommendation: "Verify DB credentials in environment variables"
|
|
69
|
+
});
|
|
70
|
+
} else {
|
|
71
|
+
logger.error("DatabaseClient: Unable to connect to database", error);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
logger.error("DatabaseClient: Unable to connect to database", error);
|
|
75
|
+
}
|
|
48
76
|
throw error;
|
|
49
77
|
}
|
|
50
78
|
}
|
|
@@ -77,6 +105,50 @@ export class DatabaseClient {
|
|
|
77
105
|
}
|
|
78
106
|
return DatabaseClient.instance;
|
|
79
107
|
}
|
|
108
|
+
static setupPoolMonitoring() {
|
|
109
|
+
if (!DatabaseClient.instance) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const pool = DatabaseClient.instance.connectionManager?.pool;
|
|
113
|
+
if (!pool) {
|
|
114
|
+
logger.warn("DatabaseClient: Connection pool not available for monitoring");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Log pool stats every 60 seconds in production
|
|
118
|
+
if (process.env.NODE_ENV === "production") {
|
|
119
|
+
setInterval(()=>{
|
|
120
|
+
try {
|
|
121
|
+
const stats = {
|
|
122
|
+
size: pool.size,
|
|
123
|
+
available: pool.available,
|
|
124
|
+
using: pool.using,
|
|
125
|
+
waiting: pool.waiting
|
|
126
|
+
};
|
|
127
|
+
// Only log if pool is being used or has waiting requests
|
|
128
|
+
if (stats.using > 0 || stats.waiting > 0) {
|
|
129
|
+
logger.info("DatabaseClient: Pool stats", stats);
|
|
130
|
+
}
|
|
131
|
+
// Warn if pool is near capacity
|
|
132
|
+
if (stats.using >= pool.max * 0.8) {
|
|
133
|
+
logger.warn("DatabaseClient: Pool usage high", {
|
|
134
|
+
...stats,
|
|
135
|
+
capacityPercent: Math.round(stats.using / pool.max * 100)
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// Alert if requests are waiting
|
|
139
|
+
if (stats.waiting > 0) {
|
|
140
|
+
logger.error("DatabaseClient: Connections waiting for pool", {
|
|
141
|
+
...stats,
|
|
142
|
+
message: "Consider increasing DB_POOL_MAX"
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
logger.debug("DatabaseClient: Error reading pool stats", error);
|
|
147
|
+
}
|
|
148
|
+
}, 60000); // Every 60 seconds
|
|
149
|
+
}
|
|
150
|
+
logger.info("DatabaseClient: Pool monitoring enabled");
|
|
151
|
+
}
|
|
80
152
|
}
|
|
81
153
|
|
|
82
154
|
//# sourceMappingURL=Database.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/classes/Database.ts"],"sourcesContent":["import { Sequelize, type SequelizeOptions, type ModelCtor } from \"sequelize-typescript\";\nimport { Log } from \"../utils/Logger.js\";\n\nconst logger = Log.getInstance();\n\nexport interface DatabaseConfig {\n DATABASE_URL: string;\n DB_POOL_MAX?: number;\n DB_POOL_MIN?: number;\n DB_ACQUIRE_TIMEOUT?: number;\n DB_IDLE_TIMEOUT?: number;\n logging?: boolean;\n}\n\nexport class DatabaseClient {\n private static instance: Sequelize | null = null;\n private static isConnected: boolean = false;\n\n static getInstance(\n config?: DatabaseConfig,\n models?: ModelCtor[],\n ): Sequelize {\n if (!DatabaseClient.instance) {\n if (!config) {\n throw new Error(\n \"DatabaseClient: config is required for first initialization\",\n );\n }\n\n const options: SequelizeOptions = {\n dialect: \"postgres\",\n dialectOptions: {\n socketPath: new URL(config.DATABASE_URL).hostname,\n },\n pool: {\n max: config.DB_POOL_MAX || 10,\n min: config.DB_POOL_MIN || 0,\n acquire: config.DB_ACQUIRE_TIMEOUT || 60000,\n idle: config.DB_IDLE_TIMEOUT || 10000,\n },\n logging: config.logging !== false ? (msg: string) => logger.debug(msg) : false,\n models: models || [],\n define: {\n timestamps: false\n }\n };\n\n DatabaseClient.instance = new Sequelize(config.DATABASE_URL, options);\n logger.info(\"DatabaseClient: Sequelize instance created\");\n } else if (models && models.length > 0) {\n DatabaseClient.instance.addModels(models);\n logger.info(`DatabaseClient: Added ${models.length} additional models`);\n }\n\n return DatabaseClient.instance;\n }\n\n static async authenticate(): Promise<void> {\n if (!DatabaseClient.instance) {\n throw new Error(\n \"DatabaseClient: getInstance() must be called before authenticate()\",\n );\n }\n\n try {\n await DatabaseClient.instance.authenticate();\n DatabaseClient.isConnected = true;\n logger.info(\"DatabaseClient: Connection authenticated successfully\");\n } catch (error) {\n DatabaseClient.isConnected = false;\n logger.error(\"DatabaseClient: Unable to connect to database\", error);\n throw error;\n }\n }\n\n\n static async close(): Promise<void> {\n if (DatabaseClient.instance) {\n await DatabaseClient.instance.close();\n DatabaseClient.instance = null;\n DatabaseClient.isConnected = false;\n logger.info(\"DatabaseClient: Connection closed\");\n }\n }\n\n static async sync(options?: { force?: boolean; alter?: boolean }): Promise<void> {\n if (!DatabaseClient.instance) {\n throw new Error(\n \"DatabaseClient: getInstance() must be called before sync()\",\n );\n }\n\n try {\n await DatabaseClient.instance.sync(options);\n logger.info(\"DatabaseClient: Models synced with database\", options);\n } catch (error) {\n logger.error(\"DatabaseClient: Failed to sync models\", error);\n throw error;\n }\n }\n\n\n static isConnectedToDatabase(): boolean {\n return DatabaseClient.isConnected;\n }\n\n\n static getSequelizeInstance(): Sequelize {\n if (!DatabaseClient.instance) {\n throw new Error(\n \"DatabaseClient: getInstance() must be called before getSequelizeInstance()\",\n );\n }\n return DatabaseClient.instance;\n }\n}\n"],"names":["Sequelize","Log","logger","getInstance","DatabaseClient","instance","isConnected","config","models","Error","options","dialect","dialectOptions","socketPath","URL","DATABASE_URL","hostname","pool","max","DB_POOL_MAX","min","DB_POOL_MIN","acquire","DB_ACQUIRE_TIMEOUT","idle","DB_IDLE_TIMEOUT","logging","msg","debug","define","timestamps","info","length","addModels","authenticate","error","close","sync","isConnectedToDatabase","getSequelizeInstance"],"mappings":"AAAA,SAASA,SAAS,QAA+C,uBAAuB;AACxF,SAASC,GAAG,QAAQ,qBAAqB;AAEzC,MAAMC,SAASD,IAAIE,WAAW;AAW9B,OAAO,MAAMC;IACT,OAAeC,WAA6B,KAAK;IACjD,OAAeC,cAAuB,MAAM;IAE5C,OAAOH,YACHI,MAAuB,EACvBC,MAAoB,EACX;QACT,IAAI,CAACJ,eAAeC,QAAQ,EAAE;YAC1B,IAAI,CAACE,QAAQ;gBACT,MAAM,IAAIE,MACN;YAER;YAEA,MAAMC,UAA4B;gBAC9BC,SAAS;gBACTC,gBAAgB;oBACZC,YAAY,IAAIC,IAAIP,OAAOQ,YAAY,EAAEC,QAAQ;gBACrD;gBACAC,MAAM;oBACFC,KAAKX,OAAOY,WAAW,IAAI;oBAC3BC,KAAKb,OAAOc,WAAW,IAAI;oBAC3BC,SAASf,OAAOgB,kBAAkB,IAAI;oBACtCC,MAAMjB,OAAOkB,eAAe,IAAI;gBACpC;gBACAC,SAASnB,OAAOmB,OAAO,KAAK,QAAQ,CAACC,MAAgBzB,OAAO0B,KAAK,CAACD,OAAO;gBACzEnB,QAAQA,UAAU,EAAE;gBACpBqB,QAAQ;oBACJC,YAAY;gBAChB;YACJ;YAEA1B,eAAeC,QAAQ,GAAG,IAAIL,UAAUO,OAAOQ,YAAY,EAAEL;YAC7DR,OAAO6B,IAAI,CAAC;QAChB,OAAO,IAAIvB,UAAUA,OAAOwB,MAAM,GAAG,GAAG;YACpC5B,eAAeC,QAAQ,CAAC4B,SAAS,CAACzB;YAClCN,OAAO6B,IAAI,CAAC,CAAC,sBAAsB,EAAEvB,OAAOwB,MAAM,CAAC,kBAAkB,CAAC;QAC1E;QAEA,OAAO5B,eAAeC,QAAQ;IAClC;IAEA,aAAa6B,eAA8B;QACvC,IAAI,CAAC9B,eAAeC,QAAQ,EAAE;YAC1B,MAAM,IAAII,MACN;QAER;QAEA,IAAI;YACA,MAAML,eAAeC,QAAQ,CAAC6B,YAAY;YAC1C9B,eAAeE,WAAW,GAAG;YAC7BJ,OAAO6B,IAAI,CAAC;QAChB,EAAE,OAAOI,OAAO;YACZ/B,eAAeE,WAAW,GAAG;YAC7BJ,OAAOiC,KAAK,CAAC,iDAAiDA;YAC9D,MAAMA;QACV;IACJ;IAGA,aAAaC,QAAuB;QAChC,IAAIhC,eAAeC,QAAQ,EAAE;YACzB,MAAMD,eAAeC,QAAQ,CAAC+B,KAAK;YACnChC,eAAeC,QAAQ,GAAG;YAC1BD,eAAeE,WAAW,GAAG;YAC7BJ,OAAO6B,IAAI,CAAC;QAChB;IACJ;IAEA,aAAaM,KAAK3B,OAA8C,EAAiB;QAC7E,IAAI,CAACN,eAAeC,QAAQ,EAAE;YAC1B,MAAM,IAAII,MACN;QAER;QAEA,IAAI;YACA,MAAML,eAAeC,QAAQ,CAACgC,IAAI,CAAC3B;YACnCR,OAAO6B,IAAI,CAAC,+CAA+CrB;QAC/D,EAAE,OAAOyB,OAAO;YACZjC,OAAOiC,KAAK,CAAC,yCAAyCA;YACtD,MAAMA;QACV;IACJ;IAGA,OAAOG,wBAAiC;QACpC,OAAOlC,eAAeE,WAAW;IACrC;IAGA,OAAOiC,uBAAkC;QACrC,IAAI,CAACnC,eAAeC,QAAQ,EAAE;YAC1B,MAAM,IAAII,MACN;QAER;QACA,OAAOL,eAAeC,QAAQ;IAClC;AACJ"}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/classes/Database.ts"],"sourcesContent":["import { Sequelize, type SequelizeOptions, type ModelCtor } from \"sequelize-typescript\";\nimport { Log } from \"../utils/Logger.js\";\n\nconst logger = Log.getInstance();\n\nexport interface DatabaseConfig {\n DATABASE_URL: string;\n DB_POOL_MAX?: number;\n DB_POOL_MIN?: number;\n DB_ACQUIRE_TIMEOUT?: number;\n DB_IDLE_TIMEOUT?: number;\n DB_EVICT_INTERVAL?: number;\n logging?: boolean;\n}\n\nexport class DatabaseClient {\n private static instance: Sequelize | null = null;\n private static isConnected: boolean = false;\n\n static getInstance(\n config?: DatabaseConfig,\n models?: ModelCtor[],\n ): Sequelize {\n if (!DatabaseClient.instance) {\n if (!config) {\n throw new Error(\n \"DatabaseClient: config is required for first initialization\",\n );\n }\n\n const poolConfig = {\n max: config.DB_POOL_MAX || 10,\n min: config.DB_POOL_MIN || 0,\n acquire: config.DB_ACQUIRE_TIMEOUT || 60000,\n idle: config.DB_IDLE_TIMEOUT || 10000,\n evict: config.DB_EVICT_INTERVAL || 5000,\n };\n\n const options: SequelizeOptions = {\n dialect: \"postgres\",\n dialectOptions: {\n socketPath: new URL(config.DATABASE_URL).hostname,\n },\n pool: poolConfig,\n logging: config.logging !== false ? (msg: string) => logger.debug(msg) : false,\n models: models || [],\n define: {\n timestamps: false\n }\n };\n\n DatabaseClient.instance = new Sequelize(config.DATABASE_URL, options);\n logger.info(\"DatabaseClient: Sequelize instance created\", {\n poolConfig,\n });\n\n // Set up connection pool monitoring\n DatabaseClient.setupPoolMonitoring();\n } else if (models && models.length > 0) {\n DatabaseClient.instance.addModels(models);\n logger.info(`DatabaseClient: Added ${models.length} additional models`);\n }\n\n return DatabaseClient.instance;\n }\n\n static async authenticate(): Promise<void> {\n if (!DatabaseClient.instance) {\n throw new Error(\n \"DatabaseClient: getInstance() must be called before authenticate()\",\n );\n }\n\n try {\n await DatabaseClient.instance.authenticate();\n DatabaseClient.isConnected = true;\n logger.info(\"DatabaseClient: Connection authenticated successfully\");\n } catch (error) {\n DatabaseClient.isConnected = false;\n\n // Enhanced error handling for common connection issues\n if (error instanceof Error) {\n if (error.message.includes(\"too many clients\")) {\n logger.error(\"DatabaseClient: PostgreSQL connection pool exhausted\", {\n error: error.message,\n recommendation: \"Increase DB_POOL_MAX or check for connection leaks\",\n });\n } else if (error.message.includes(\"timeout\")) {\n logger.error(\"DatabaseClient: Connection timeout\", {\n error: error.message,\n recommendation: \"Check network connectivity or increase DB_ACQUIRE_TIMEOUT\",\n });\n } else if (error.message.includes(\"authentication failed\")) {\n logger.error(\"DatabaseClient: Authentication failed\", {\n error: error.message,\n recommendation: \"Verify DB credentials in environment variables\",\n });\n } else {\n logger.error(\"DatabaseClient: Unable to connect to database\", error);\n }\n } else {\n logger.error(\"DatabaseClient: Unable to connect to database\", error);\n }\n\n throw error;\n }\n }\n\n\n static async close(): Promise<void> {\n if (DatabaseClient.instance) {\n await DatabaseClient.instance.close();\n DatabaseClient.instance = null;\n DatabaseClient.isConnected = false;\n logger.info(\"DatabaseClient: Connection closed\");\n }\n }\n\n static async sync(options?: { force?: boolean; alter?: boolean }): Promise<void> {\n if (!DatabaseClient.instance) {\n throw new Error(\n \"DatabaseClient: getInstance() must be called before sync()\",\n );\n }\n\n try {\n await DatabaseClient.instance.sync(options);\n logger.info(\"DatabaseClient: Models synced with database\", options);\n } catch (error) {\n logger.error(\"DatabaseClient: Failed to sync models\", error);\n throw error;\n }\n }\n\n\n static isConnectedToDatabase(): boolean {\n return DatabaseClient.isConnected;\n }\n\n\n static getSequelizeInstance(): Sequelize {\n if (!DatabaseClient.instance) {\n throw new Error(\n \"DatabaseClient: getInstance() must be called before getSequelizeInstance()\",\n );\n }\n return DatabaseClient.instance;\n }\n\n private static setupPoolMonitoring(): void {\n if (!DatabaseClient.instance) {\n return;\n }\n\n const pool = (DatabaseClient.instance as any).connectionManager?.pool;\n if (!pool) {\n logger.warn(\"DatabaseClient: Connection pool not available for monitoring\");\n return;\n }\n\n // Log pool stats every 60 seconds in production\n if (process.env.NODE_ENV === \"production\") {\n setInterval(() => {\n try {\n const stats = {\n size: pool.size,\n available: pool.available,\n using: pool.using,\n waiting: pool.waiting,\n };\n\n // Only log if pool is being used or has waiting requests\n if (stats.using > 0 || stats.waiting > 0) {\n logger.info(\"DatabaseClient: Pool stats\", stats);\n }\n\n // Warn if pool is near capacity\n if (stats.using >= pool.max * 0.8) {\n logger.warn(\"DatabaseClient: Pool usage high\", {\n ...stats,\n capacityPercent: Math.round((stats.using / pool.max) * 100),\n });\n }\n\n // Alert if requests are waiting\n if (stats.waiting > 0) {\n logger.error(\"DatabaseClient: Connections waiting for pool\", {\n ...stats,\n message: \"Consider increasing DB_POOL_MAX\",\n });\n }\n } catch (error) {\n logger.debug(\"DatabaseClient: Error reading pool stats\", error);\n }\n }, 60000); // Every 60 seconds\n }\n\n logger.info(\"DatabaseClient: Pool monitoring enabled\");\n }\n}\n"],"names":["Sequelize","Log","logger","getInstance","DatabaseClient","instance","isConnected","config","models","Error","poolConfig","max","DB_POOL_MAX","min","DB_POOL_MIN","acquire","DB_ACQUIRE_TIMEOUT","idle","DB_IDLE_TIMEOUT","evict","DB_EVICT_INTERVAL","options","dialect","dialectOptions","socketPath","URL","DATABASE_URL","hostname","pool","logging","msg","debug","define","timestamps","info","setupPoolMonitoring","length","addModels","authenticate","error","message","includes","recommendation","close","sync","isConnectedToDatabase","getSequelizeInstance","connectionManager","warn","process","env","NODE_ENV","setInterval","stats","size","available","using","waiting","capacityPercent","Math","round"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorMiddleware.d.ts","sourceRoot":"","sources":["../../../src/lib/middlewares/ErrorMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAc,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"ErrorMiddleware.d.ts","sourceRoot":"","sources":["../../../src/lib/middlewares/ErrorMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAc,MAAM,uBAAuB,CAAC;AAa7D,MAAM,WAAW,qBAAqB;IACpC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IACvD,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,wBAAgB,eAAe,CAAC,MAAM,GAAE,qBAA0B,IAWlD,KAAK,OAAO,EAAE,MAAM,IAAI,mBAmGvC;AA4DD,wBAAgB,eAAe,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,GAAG;IAClD,gBAAgB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACrE,CAQA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { AppError, toAppError } from "../Errors/AppError.js";
|
|
3
|
-
import { initializeSentry, isSentryEnabled, reportErrorToSentry, reportMessageToSentry } from "../utils/SentryUtil.js";
|
|
3
|
+
import { initializeSentry, isSentryEnabled, reportErrorToSentry, reportMessageToSentry, scrubObject, captureRequestBody } from "../utils/SentryUtil.js";
|
|
4
4
|
import { Log } from "../utils/Logger.js";
|
|
5
5
|
const dclogger = Log.getInstance().extend("ErrorMiddleware");
|
|
6
6
|
export function ErrorMiddleware(config = {}) {
|
|
@@ -16,11 +16,13 @@ export function ErrorMiddleware(config = {}) {
|
|
|
16
16
|
const requestContext = {
|
|
17
17
|
method: ctx.method,
|
|
18
18
|
path: ctx.path,
|
|
19
|
-
body: ctx.request.body,
|
|
20
|
-
query: ctx.query,
|
|
21
|
-
headers: ctx.headers,
|
|
19
|
+
body: captureRequestBody(ctx.request.body),
|
|
20
|
+
query: scrubObject(ctx.query),
|
|
21
|
+
headers: scrubObject(ctx.headers),
|
|
22
22
|
userUuid: ctx.state.user?.userUuid,
|
|
23
|
-
validationErrors: validationErrors.validationErrors
|
|
23
|
+
validationErrors: validationErrors.validationErrors,
|
|
24
|
+
traceId: ctx.state.traceId,
|
|
25
|
+
ip: ctx.ip
|
|
24
26
|
};
|
|
25
27
|
// Log validation error with full request context and validation details
|
|
26
28
|
dclogger.error("Validation error", requestContext);
|
|
@@ -36,11 +38,14 @@ export function ErrorMiddleware(config = {}) {
|
|
|
36
38
|
const requestContext = {
|
|
37
39
|
method: ctx.method,
|
|
38
40
|
path: ctx.path,
|
|
39
|
-
body: ctx.request.body,
|
|
40
|
-
query: ctx.query,
|
|
41
|
-
headers: ctx.headers,
|
|
41
|
+
body: captureRequestBody(ctx.request.body),
|
|
42
|
+
query: scrubObject(ctx.query),
|
|
43
|
+
headers: scrubObject(ctx.headers),
|
|
44
|
+
userUuid: ctx.state.user?.userUuid,
|
|
42
45
|
error: err instanceof Error ? err.message : String(err),
|
|
43
|
-
stack: err instanceof Error ? err.stack : undefined
|
|
46
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
47
|
+
traceId: ctx.state.traceId,
|
|
48
|
+
ip: ctx.ip
|
|
44
49
|
};
|
|
45
50
|
// Log error with full request context
|
|
46
51
|
dclogger.error("Error processing request", requestContext);
|
|
@@ -63,7 +68,17 @@ export function ErrorMiddleware(config = {}) {
|
|
|
63
68
|
ctx.status = appError.statusCode;
|
|
64
69
|
ctx.set("X-Error-Code", appError.code);
|
|
65
70
|
ctx.type = "application/json";
|
|
66
|
-
|
|
71
|
+
const responseBody = formatter ? formatter(appError, ctx) : formatErrorResponse(appError, exposeErrorDetails);
|
|
72
|
+
ctx.body = responseBody;
|
|
73
|
+
// Log response body for debugging (only in non-production or for server errors)
|
|
74
|
+
if (process.env.NODE_ENV !== "production" || appError.statusCode >= 500) {
|
|
75
|
+
dclogger.error("Error response sent", {
|
|
76
|
+
statusCode: appError.statusCode,
|
|
77
|
+
errorCode: appError.code,
|
|
78
|
+
responseBody: scrubObject(responseBody),
|
|
79
|
+
traceId: ctx.state.traceId
|
|
80
|
+
});
|
|
81
|
+
}
|
|
67
82
|
}
|
|
68
83
|
};
|
|
69
84
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/middlewares/ErrorMiddleware.ts"],"sourcesContent":["import type { Context, Next } from \"koa\";\nimport { z } from \"zod\";\nimport { AppError, toAppError } from \"../Errors/AppError.js\";\nimport {\n initializeSentry,\n isSentryEnabled,\n reportErrorToSentry,\n reportMessageToSentry,\n} from \"../utils/SentryUtil.js\";\nimport { Log } from \"../utils/Logger.js\";\n\nconst dclogger = Log.getInstance().extend(\"ErrorMiddleware\");\n\nexport interface ErrorMiddlewareConfig {\n exposeErrorDetails?: boolean;\n logErrors?: boolean;\n logger?: (error: Error, ctx: Context) => void;\n formatter?: (error: AppError, ctx: Context) => unknown;\n disableSentry?: boolean;\n}\n\nexport function ErrorMiddleware(config: ErrorMiddlewareConfig = {}) {\n const {\n exposeErrorDetails = false,\n logErrors = true,\n logger,\n formatter,\n disableSentry = false,\n } = config;\n\n initializeSentry();\n\n return async (ctx: Context, next: Next) => {\n try {\n await next();\n } catch (err) {\n let appError: AppError;\n\n if (err instanceof z.ZodError) {\n const validationErrors = formatZodErrors(err);\n const requestContext = {\n method: ctx.method,\n path: ctx.path,\n body: (ctx.request as any).body,\n query: ctx.query,\n headers: ctx.headers,\n userUuid: ctx.state.user?.userUuid,\n validationErrors: validationErrors.validationErrors,\n };\n\n // Log validation error with full request context and validation details\n dclogger.error(\"Validation error\", requestContext);\n\n // Report to Sentry with full context\n if (!disableSentry && isSentryEnabled()) {\n reportMessageToSentry(\n \"Validation error\",\n \"error\",\n {\n request: requestContext,\n validationErrors,\n }\n );\n }\n\n appError = new AppError(\n \"VALIDATION_ERROR\",\n \"Request validation failed\",\n 400,\n validationErrors\n );\n } else {\n const requestContext = {\n method: ctx.method,\n path: ctx.path,\n body: (ctx.request as any).body,\n query: ctx.query,\n headers: ctx.headers,\n error: err instanceof Error ? err.message : String(err),\n stack: err instanceof Error ? err.stack : undefined,\n };\n\n // Log error with full request context\n dclogger.error(\"Error processing request\", requestContext);\n\n appError = toAppError(err);\n }\n\n if (!disableSentry && isSentryEnabled() && appError.statusCode !== 401) {\n reportErrorToSentry(appError, ctx, {\n operational: appError.isOperational,\n errorType: appError.constructor.name,\n statusCode: appError.statusCode,\n });\n }\n\n if (logErrors) {\n if (logger) {\n logger(appError, ctx);\n } else {\n defaultErrorLogger(appError, ctx);\n }\n }\n\n ctx.status = appError.statusCode;\n ctx.set(\"X-Error-Code\", appError.code);\n ctx.type = \"application/json\";\n\n ctx.body = formatter\n ? formatter(appError, ctx)\n : formatErrorResponse(appError, exposeErrorDetails);\n\n }\n };\n}\n\nfunction defaultErrorLogger(error: AppError, ctx: Context): void {\n const isDev = process.env.NODE_ENV === \"development\";\n\n if (error.statusCode >= 500) {\n if (!isDev) {\n const prodLogData = {\n error: {\n code: error.code,\n message: error.message,\n statusCode: error.statusCode,\n },\n request: {\n path: ctx.path,\n },\n user: {\n uuid: ctx.state.user?.userUuid,\n },\n traceId: ctx.state.traceId,\n };\n console.error(\"Server Error:\", JSON.stringify(prodLogData, null, 2));\n } else {\n console.error(\"Server Error:\", error.stack || error.message);\n }\n } else {\n console.warn(\"Client Error:\", JSON.stringify({\n code: error.code,\n message: error.message,\n statusCode: error.statusCode,\n path: ctx.path,\n }));\n }\n}\n\n\nfunction formatErrorResponse(error: AppError, exposeDetails: boolean): unknown {\n const response: {\n code: string;\n message: string;\n details?: unknown;\n stack?: string;\n } = {\n code: error.code,\n message: error.message,\n };\n\n if (exposeDetails || error.statusCode < 500) {\n if (error.details !== undefined) {\n response.details = error.details;\n }\n }\n\n if (exposeDetails && error.stack) {\n response.stack = error.stack;\n }\n\n return response;\n}\n\nexport function formatZodErrors(error: z.ZodError): {\n validationErrors: { path: string; message: string; code: string }[];\n} {\n return {\n validationErrors: error.issues.map((issue) => ({\n path: issue.path.join(\".\"),\n message: issue.message,\n code: issue.code,\n })),\n };\n}"],"names":["z","AppError","toAppError","initializeSentry","isSentryEnabled","reportErrorToSentry","reportMessageToSentry","Log","dclogger","getInstance","extend","ErrorMiddleware","config","exposeErrorDetails","logErrors","logger","formatter","disableSentry","ctx","next","err","appError","ZodError","validationErrors","formatZodErrors","requestContext","method","path","body","request","query","headers","userUuid","state","user","error","Error","message","String","stack","undefined","statusCode","operational","isOperational","errorType","name","defaultErrorLogger","status","set","code","type","formatErrorResponse","isDev","process","env","NODE_ENV","prodLogData","uuid","traceId","console","JSON","stringify","warn","exposeDetails","response","details","issues","map","issue","join"],"mappings":"AACA,SAASA,CAAC,QAAQ,MAAM;AACxB,SAASC,QAAQ,EAAEC,UAAU,QAAQ,wBAAwB;AAC7D,SACEC,gBAAgB,EAChBC,eAAe,EACfC,mBAAmB,EACnBC,qBAAqB,QAChB,yBAAyB;AAChC,SAASC,GAAG,QAAQ,qBAAqB;AAEzC,MAAMC,WAAWD,IAAIE,WAAW,GAAGC,MAAM,CAAC;AAU1C,OAAO,SAASC,gBAAgBC,SAAgC,CAAC,CAAC;IAChE,MAAM,EACJC,qBAAqB,KAAK,EAC1BC,YAAY,IAAI,EAChBC,MAAM,EACNC,SAAS,EACTC,gBAAgB,KAAK,EACtB,GAAGL;IAEJT;IAEA,OAAO,OAAOe,KAAcC;QAC1B,IAAI;YACF,MAAMA;QACR,EAAE,OAAOC,KAAK;YACZ,IAAIC;YAEJ,IAAID,eAAepB,EAAEsB,QAAQ,EAAE;gBAC7B,MAAMC,mBAAmBC,gBAAgBJ;gBACzC,MAAMK,iBAAiB;oBACrBC,QAAQR,IAAIQ,MAAM;oBAClBC,MAAMT,IAAIS,IAAI;oBACdC,MAAM,AAACV,IAAIW,OAAO,CAASD,IAAI;oBAC/BE,OAAOZ,IAAIY,KAAK;oBAChBC,SAASb,IAAIa,OAAO;oBACpBC,UAAUd,IAAIe,KAAK,CAACC,IAAI,EAAEF;oBAC1BT,kBAAkBA,iBAAiBA,gBAAgB;gBACrD;gBAEA,wEAAwE;gBACxEf,SAAS2B,KAAK,CAAC,oBAAoBV;gBAEnC,qCAAqC;gBACrC,IAAI,CAACR,iBAAiBb,mBAAmB;oBACvCE,sBACE,oBACA,SACA;wBACEuB,SAASJ;wBACTF;oBACF;gBAEJ;gBAEAF,WAAW,IAAIpB,SACX,oBACA,6BACA,KACAsB;YAEN,OAAO;gBACL,MAAME,iBAAiB;oBACrBC,QAAQR,IAAIQ,MAAM;oBAClBC,MAAMT,IAAIS,IAAI;oBACdC,MAAM,AAACV,IAAIW,OAAO,CAASD,IAAI;oBAC/BE,OAAOZ,IAAIY,KAAK;oBAChBC,SAASb,IAAIa,OAAO;oBACpBI,OAAOf,eAAegB,QAAQhB,IAAIiB,OAAO,GAAGC,OAAOlB;oBACnDmB,OAAOnB,eAAegB,QAAQhB,IAAImB,KAAK,GAAGC;gBAC5C;gBAEA,sCAAsC;gBACtChC,SAAS2B,KAAK,CAAC,4BAA4BV;gBAE3CJ,WAAWnB,WAAWkB;YACxB;YAEA,IAAI,CAACH,iBAAiBb,qBAAqBiB,SAASoB,UAAU,KAAK,KAAK;gBACtEpC,oBAAoBgB,UAAUH,KAAK;oBACjCwB,aAAarB,SAASsB,aAAa;oBACnCC,WAAWvB,SAAS,WAAW,CAACwB,IAAI;oBACpCJ,YAAYpB,SAASoB,UAAU;gBACjC;YACF;YAEA,IAAI3B,WAAW;gBACb,IAAIC,QAAQ;oBACVA,OAAOM,UAAUH;gBACnB,OAAO;oBACL4B,mBAAmBzB,UAAUH;gBAC/B;YACF;YAEAA,IAAI6B,MAAM,GAAG1B,SAASoB,UAAU;YAChCvB,IAAI8B,GAAG,CAAC,gBAAgB3B,SAAS4B,IAAI;YACrC/B,IAAIgC,IAAI,GAAG;YAEXhC,IAAIU,IAAI,GAAGZ,YACLA,UAAUK,UAAUH,OACpBiC,oBAAoB9B,UAAUR;QAEtC;IACF;AACF;AAEA,SAASiC,mBAAmBX,KAAe,EAAEjB,GAAY;IACvD,MAAMkC,QAAQC,QAAQC,GAAG,CAACC,QAAQ,KAAK;IAEvC,IAAIpB,MAAMM,UAAU,IAAI,KAAK;QAC3B,IAAI,CAACW,OAAO;YACV,MAAMI,cAAc;gBAClBrB,OAAO;oBACLc,MAAMd,MAAMc,IAAI;oBAChBZ,SAASF,MAAME,OAAO;oBACtBI,YAAYN,MAAMM,UAAU;gBAC9B;gBACAZ,SAAS;oBACPF,MAAMT,IAAIS,IAAI;gBAChB;gBACAO,MAAM;oBACJuB,MAAMvC,IAAIe,KAAK,CAACC,IAAI,EAAEF;gBACxB;gBACA0B,SAASxC,IAAIe,KAAK,CAACyB,OAAO;YAC5B;YACAC,QAAQxB,KAAK,CAAC,iBAAiByB,KAAKC,SAAS,CAACL,aAAa,MAAM;QACnE,OAAO;YACLG,QAAQxB,KAAK,CAAC,iBAAiBA,MAAMI,KAAK,IAAIJ,MAAME,OAAO;QAC7D;IACF,OAAO;QACLsB,QAAQG,IAAI,CAAC,iBAAiBF,KAAKC,SAAS,CAAC;YAC3CZ,MAAMd,MAAMc,IAAI;YAChBZ,SAASF,MAAME,OAAO;YACtBI,YAAYN,MAAMM,UAAU;YAC5Bd,MAAMT,IAAIS,IAAI;QAChB;IACF;AACF;AAGA,SAASwB,oBAAoBhB,KAAe,EAAE4B,aAAsB;IAClE,MAAMC,WAKF;QACAf,MAAMd,MAAMc,IAAI;QAChBZ,SAASF,MAAME,OAAO;IAC1B;IAEA,IAAI0B,iBAAiB5B,MAAMM,UAAU,GAAG,KAAK;QAC3C,IAAIN,MAAM8B,OAAO,KAAKzB,WAAW;YAC/BwB,SAASC,OAAO,GAAG9B,MAAM8B,OAAO;QAClC;IACF;IAEA,IAAIF,iBAAiB5B,MAAMI,KAAK,EAAE;QAChCyB,SAASzB,KAAK,GAAGJ,MAAMI,KAAK;IAC9B;IAEA,OAAOyB;AACT;AAEA,OAAO,SAASxC,gBAAgBW,KAAiB;IAG/C,OAAO;QACLZ,kBAAkBY,MAAM+B,MAAM,CAACC,GAAG,CAAC,CAACC,QAAW,CAAA;gBAC7CzC,MAAMyC,MAAMzC,IAAI,CAAC0C,IAAI,CAAC;gBACtBhC,SAAS+B,MAAM/B,OAAO;gBACtBY,MAAMmB,MAAMnB,IAAI;YAClB,CAAA;IACF;AACF"}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/middlewares/ErrorMiddleware.ts"],"sourcesContent":["import type { Context, Next } from \"koa\";\nimport { z } from \"zod\";\nimport { AppError, toAppError } from \"../Errors/AppError.js\";\nimport {\n initializeSentry,\n isSentryEnabled,\n reportErrorToSentry,\n reportMessageToSentry,\n scrubObject,\n captureRequestBody,\n} from \"../utils/SentryUtil.js\";\nimport { Log } from \"../utils/Logger.js\";\n\nconst dclogger = Log.getInstance().extend(\"ErrorMiddleware\");\n\nexport interface ErrorMiddlewareConfig {\n exposeErrorDetails?: boolean;\n logErrors?: boolean;\n logger?: (error: Error, ctx: Context) => void;\n formatter?: (error: AppError, ctx: Context) => unknown;\n disableSentry?: boolean;\n}\n\nexport function ErrorMiddleware(config: ErrorMiddlewareConfig = {}) {\n const {\n exposeErrorDetails = false,\n logErrors = true,\n logger,\n formatter,\n disableSentry = false,\n } = config;\n\n initializeSentry();\n\n return async (ctx: Context, next: Next) => {\n try {\n await next();\n } catch (err) {\n let appError: AppError;\n\n if (err instanceof z.ZodError) {\n const validationErrors = formatZodErrors(err);\n const requestContext = {\n method: ctx.method,\n path: ctx.path,\n body: captureRequestBody((ctx.request as any).body),\n query: scrubObject(ctx.query),\n headers: scrubObject(ctx.headers),\n userUuid: ctx.state.user?.userUuid,\n validationErrors: validationErrors.validationErrors,\n traceId: ctx.state.traceId,\n ip: ctx.ip,\n };\n\n // Log validation error with full request context and validation details\n dclogger.error(\"Validation error\", requestContext);\n\n // Report to Sentry with full context\n if (!disableSentry && isSentryEnabled()) {\n reportMessageToSentry(\n \"Validation error\",\n \"error\",\n {\n request: requestContext,\n validationErrors,\n }\n );\n }\n\n appError = new AppError(\n \"VALIDATION_ERROR\",\n \"Request validation failed\",\n 400,\n validationErrors\n );\n } else {\n const requestContext = {\n method: ctx.method,\n path: ctx.path,\n body: captureRequestBody((ctx.request as any).body),\n query: scrubObject(ctx.query),\n headers: scrubObject(ctx.headers),\n userUuid: ctx.state.user?.userUuid,\n error: err instanceof Error ? err.message : String(err),\n stack: err instanceof Error ? err.stack : undefined,\n traceId: ctx.state.traceId,\n ip: ctx.ip,\n };\n\n // Log error with full request context\n dclogger.error(\"Error processing request\", requestContext);\n\n appError = toAppError(err);\n }\n\n if (!disableSentry && isSentryEnabled() && appError.statusCode !== 401) {\n reportErrorToSentry(appError, ctx, {\n operational: appError.isOperational,\n errorType: appError.constructor.name,\n statusCode: appError.statusCode,\n });\n }\n\n if (logErrors) {\n if (logger) {\n logger(appError, ctx);\n } else {\n defaultErrorLogger(appError, ctx);\n }\n }\n\n ctx.status = appError.statusCode;\n ctx.set(\"X-Error-Code\", appError.code);\n ctx.type = \"application/json\";\n\n const responseBody = formatter\n ? formatter(appError, ctx)\n : formatErrorResponse(appError, exposeErrorDetails);\n\n ctx.body = responseBody;\n\n // Log response body for debugging (only in non-production or for server errors)\n if (process.env.NODE_ENV !== \"production\" || appError.statusCode >= 500) {\n dclogger.error(\"Error response sent\", {\n statusCode: appError.statusCode,\n errorCode: appError.code,\n responseBody: scrubObject(responseBody),\n traceId: ctx.state.traceId,\n });\n }\n\n }\n };\n}\n\nfunction defaultErrorLogger(error: AppError, ctx: Context): void {\n const isDev = process.env.NODE_ENV === \"development\";\n\n if (error.statusCode >= 500) {\n if (!isDev) {\n const prodLogData = {\n error: {\n code: error.code,\n message: error.message,\n statusCode: error.statusCode,\n },\n request: {\n path: ctx.path,\n },\n user: {\n uuid: ctx.state.user?.userUuid,\n },\n traceId: ctx.state.traceId,\n };\n console.error(\"Server Error:\", JSON.stringify(prodLogData, null, 2));\n } else {\n console.error(\"Server Error:\", error.stack || error.message);\n }\n } else {\n console.warn(\"Client Error:\", JSON.stringify({\n code: error.code,\n message: error.message,\n statusCode: error.statusCode,\n path: ctx.path,\n }));\n }\n}\n\n\nfunction formatErrorResponse(error: AppError, exposeDetails: boolean): unknown {\n const response: {\n code: string;\n message: string;\n details?: unknown;\n stack?: string;\n } = {\n code: error.code,\n message: error.message,\n };\n\n if (exposeDetails || error.statusCode < 500) {\n if (error.details !== undefined) {\n response.details = error.details;\n }\n }\n\n if (exposeDetails && error.stack) {\n response.stack = error.stack;\n }\n\n return response;\n}\n\nexport function formatZodErrors(error: z.ZodError): {\n validationErrors: { path: string; message: string; code: string }[];\n} {\n return {\n validationErrors: error.issues.map((issue) => ({\n path: issue.path.join(\".\"),\n message: issue.message,\n code: issue.code,\n })),\n };\n}"],"names":["z","AppError","toAppError","initializeSentry","isSentryEnabled","reportErrorToSentry","reportMessageToSentry","scrubObject","captureRequestBody","Log","dclogger","getInstance","extend","ErrorMiddleware","config","exposeErrorDetails","logErrors","logger","formatter","disableSentry","ctx","next","err","appError","ZodError","validationErrors","formatZodErrors","requestContext","method","path","body","request","query","headers","userUuid","state","user","traceId","ip","error","Error","message","String","stack","undefined","statusCode","operational","isOperational","errorType","name","defaultErrorLogger","status","set","code","type","responseBody","formatErrorResponse","process","env","NODE_ENV","errorCode","isDev","prodLogData","uuid","console","JSON","stringify","warn","exposeDetails","response","details","issues","map","issue","join"],"mappings":"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"}
|
|
@@ -54,7 +54,7 @@ export function scrubObject(obj, depth = 0) {
|
|
|
54
54
|
}
|
|
55
55
|
return scrubbedObj;
|
|
56
56
|
}
|
|
57
|
-
const MAX_BODY_SIZE =
|
|
57
|
+
const MAX_BODY_SIZE = 100 * 1024; // 100KB - increased from 10KB for full payload visibility
|
|
58
58
|
export function captureRequestBody(body) {
|
|
59
59
|
if (!body) {
|
|
60
60
|
return body;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils/SentryUtil.ts"],"sourcesContent":["import * as Sentry from \"@sentry/node\";\nimport type { Context } from \"koa\";\n\nlet sentryInitialized = false;\n\nexport function initializeSentry(): void {\n if (sentryInitialized) {\n return;\n }\n\n const dsn = process.env.SENTRY_DSN;\n const nodeEnv = process.env.NODE_ENV;\n\n if (!dsn || nodeEnv !== \"production\") {\n return;\n }\n\n Sentry.init({\n dsn,\n environment: nodeEnv,\n enabled: true,\n tracesSampleRate: 0,\n });\n\n sentryInitialized = true;\n}\n\nexport function isSentryEnabled(): boolean {\n return sentryInitialized;\n}\n\nconst SENSITIVE_FIELDS = [\n \"password\",\n \"token\",\n \"apikey\",\n \"secret\",\n \"cookie\",\n];\n\nexport function scrubObject(obj: any, depth = 0): any {\n if (depth > 10) {\n return \"[Max Depth]\";\n }\n\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (typeof obj !== \"object\") {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => scrubObject(item, depth + 1));\n }\n\n const scrubbedObj: any = {};\n for (const [key, value] of Object.entries(obj)) {\n const keyLower = key.toLowerCase();\n const isSensitive = SENSITIVE_FIELDS.some((field) =>\n keyLower.includes(field),\n );\n\n if (isSensitive) {\n scrubbedObj[key] = \"[REDACTED]\";\n } else if (typeof value === \"object\" && value !== null) {\n scrubbedObj[key] = scrubObject(value, depth + 1);\n } else {\n scrubbedObj[key] = value;\n }\n }\n\n return scrubbedObj;\n}\n\nconst MAX_BODY_SIZE =
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/utils/SentryUtil.ts"],"sourcesContent":["import * as Sentry from \"@sentry/node\";\nimport type { Context } from \"koa\";\n\nlet sentryInitialized = false;\n\nexport function initializeSentry(): void {\n if (sentryInitialized) {\n return;\n }\n\n const dsn = process.env.SENTRY_DSN;\n const nodeEnv = process.env.NODE_ENV;\n\n if (!dsn || nodeEnv !== \"production\") {\n return;\n }\n\n Sentry.init({\n dsn,\n environment: nodeEnv,\n enabled: true,\n tracesSampleRate: 0,\n });\n\n sentryInitialized = true;\n}\n\nexport function isSentryEnabled(): boolean {\n return sentryInitialized;\n}\n\nconst SENSITIVE_FIELDS = [\n \"password\",\n \"token\",\n \"apikey\",\n \"secret\",\n \"cookie\",\n];\n\nexport function scrubObject(obj: any, depth = 0): any {\n if (depth > 10) {\n return \"[Max Depth]\";\n }\n\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (typeof obj !== \"object\") {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => scrubObject(item, depth + 1));\n }\n\n const scrubbedObj: any = {};\n for (const [key, value] of Object.entries(obj)) {\n const keyLower = key.toLowerCase();\n const isSensitive = SENSITIVE_FIELDS.some((field) =>\n keyLower.includes(field),\n );\n\n if (isSensitive) {\n scrubbedObj[key] = \"[REDACTED]\";\n } else if (typeof value === \"object\" && value !== null) {\n scrubbedObj[key] = scrubObject(value, depth + 1);\n } else {\n scrubbedObj[key] = value;\n }\n }\n\n return scrubbedObj;\n}\n\nconst MAX_BODY_SIZE = 100 * 1024; // 100KB - increased from 10KB for full payload visibility\n\nexport function captureRequestBody(body: any): any {\n if (!body) {\n return body;\n }\n\n const bodyString = JSON.stringify(body);\n if (bodyString.length > MAX_BODY_SIZE) {\n return {\n _truncated: true,\n _originalSize: bodyString.length,\n preview: bodyString.substring(0, MAX_BODY_SIZE),\n };\n }\n\n return scrubObject(body);\n}\n\nexport function extractUserContext(ctx: Context): { id?: string } {\n const user = (ctx.state as any)?.user;\n if (user?.userUuid) {\n return { id: user.userUuid };\n }\n return {};\n}\n\nexport function buildRequestContext(ctx: Context): {\n method: string;\n url: string;\n query: any;\n headers: any;\n data: any;\n ip?: string;\n} {\n return {\n method: ctx.method,\n url: ctx.url,\n query: scrubObject(ctx.query),\n headers: scrubObject(ctx.headers),\n data: captureRequestBody((ctx.request as any).body),\n ip: ctx.ip,\n };\n}\n\nexport function buildResponseContext(ctx: Context): { status_code: number } {\n return {\n status_code: ctx.status,\n };\n}\n\nexport interface ErrorReportOptions {\n operational?: boolean;\n errorType?: string;\n statusCode?: number;\n}\n\nexport function reportErrorToSentry(\n error: Error,\n ctx?: Context,\n options?: ErrorReportOptions,\n): void {\n if (!isSentryEnabled()) {\n return;\n }\n\n Sentry.withScope((scope) => {\n if (ctx) {\n scope.setContext(\"request\", buildRequestContext(ctx));\n scope.setContext(\"response\", buildResponseContext(ctx));\n scope.setUser(extractUserContext(ctx));\n }\n\n if (options?.operational !== undefined) {\n scope.setTag(\"error.operational\", options.operational);\n }\n if (options?.errorType) {\n scope.setTag(\"error.type\", options.errorType);\n }\n if (options?.statusCode) {\n scope.setTag(\"http.status_code\", options.statusCode);\n }\n\n scope.setTag(\"source\", \"middleware\");\n\n Sentry.captureException(error);\n });\n\n (error as any)._sentryReported = true;\n}\n\nexport type SeverityLevel = \"fatal\" | \"error\" | \"warning\" | \"info\" | \"debug\";\n\nexport function reportMessageToSentry(\n message: string,\n level: SeverityLevel,\n context?: Record<string, any>,\n): void {\n if (!isSentryEnabled()) {\n return;\n }\n\n Sentry.withScope((scope) => {\n if (context) {\n for (const [key, value] of Object.entries(context)) {\n if (key === \"logger_name\" || key === \"source\") {\n scope.setTag(key, value);\n } else {\n // Deeply serialize objects and arrays to avoid [Object] and [Array] in Sentry\n const serializedValue =\n typeof value === \"object\" && value !== null\n ? scrubObject(value)\n : value;\n scope.setContext(key, serializedValue);\n }\n }\n }\n\n Sentry.captureMessage(message, level);\n });\n}\n"],"names":["Sentry","sentryInitialized","initializeSentry","dsn","process","env","SENTRY_DSN","nodeEnv","NODE_ENV","init","environment","enabled","tracesSampleRate","isSentryEnabled","SENSITIVE_FIELDS","scrubObject","obj","depth","undefined","Array","isArray","map","item","scrubbedObj","key","value","Object","entries","keyLower","toLowerCase","isSensitive","some","field","includes","MAX_BODY_SIZE","captureRequestBody","body","bodyString","JSON","stringify","length","_truncated","_originalSize","preview","substring","extractUserContext","ctx","user","state","userUuid","id","buildRequestContext","method","url","query","headers","data","request","ip","buildResponseContext","status_code","status","reportErrorToSentry","error","options","withScope","scope","setContext","setUser","operational","setTag","errorType","statusCode","captureException","_sentryReported","reportMessageToSentry","message","level","context","serializedValue","captureMessage"],"mappings":"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"}
|