11import { LoggerBase } from '@crowd/logging'
2+ import {
3+ SlackChannel ,
4+ SlackMessageSection ,
5+ SlackPersona ,
6+ sendSlackNotification ,
7+ } from '@crowd/slack'
28
39import { IServiceOptions } from '../services/IServiceOptions'
410
11+ // Slack section text limit is 3000 chars. Keep conservative to account for title + formatting.
12+ const SLACK_SECTION_TEXT_LIMIT = 2800
13+
514/* eslint-disable class-methods-use-this */
615export default class ApiResponseHandler extends LoggerBase {
716 public constructor ( options : IServiceOptions ) {
@@ -21,6 +30,89 @@ export default class ApiResponseHandler extends LoggerBase {
2130 }
2231 }
2332
33+ private truncateForSlack ( text : string , maxLength : number = SLACK_SECTION_TEXT_LIMIT ) : string {
34+ if ( text . length <= maxLength ) {
35+ return text
36+ }
37+ return `${ text . substring ( 0 , maxLength - 3 ) } ...`
38+ }
39+
40+ private sendServerErrorToSlack (
41+ req ,
42+ error ,
43+ code : number ,
44+ options ?: { sql ?: string ; dbErrorMessage ?: string } ,
45+ ) : void {
46+ const sections : SlackMessageSection [ ] = [ ]
47+
48+ // Request info section
49+ sections . push ( {
50+ title : 'Request' ,
51+ text : `*Method:* \`${ req . method } \`\n*URL:* \`${ req . url } \`\n*Status Code:* \`${ code } \`` ,
52+ } )
53+
54+ // Error info section
55+ const errorName = error ?. name || 'Unknown'
56+ const errorMessage = this . truncateForSlack ( error ?. message || 'No message' , 2000 )
57+ sections . push ( {
58+ title : 'Error' ,
59+ text : `*Name:* \`${ errorName } \`\n*Message:* ${ errorMessage } ` ,
60+ } )
61+
62+ // SQL query section (for Sequelize errors)
63+ if ( options ?. sql ) {
64+ const truncatedSql = this . truncateForSlack ( options . sql , 2700 )
65+ sections . push ( {
66+ title : 'SQL Query' ,
67+ text : `\`\`\`${ truncatedSql } \`\`\`` ,
68+ } )
69+ }
70+
71+ // DB error message (for Sequelize errors)
72+ if ( options ?. dbErrorMessage ) {
73+ sections . push ( {
74+ title : 'Database Error' ,
75+ text : this . truncateForSlack ( options . dbErrorMessage , 2700 ) ,
76+ } )
77+ }
78+
79+ // Request body section (if present)
80+ if ( req . body && Object . keys ( req . body ) . length > 0 ) {
81+ const bodyStr = JSON . stringify ( req . body , null , 2 )
82+ const truncatedBody = this . truncateForSlack ( bodyStr , 2700 )
83+ sections . push ( {
84+ title : 'Request Body' ,
85+ text : `\`\`\`${ truncatedBody } \`\`\`` ,
86+ } )
87+ }
88+
89+ // Query params section (if present)
90+ if ( req . query && Object . keys ( req . query ) . length > 0 ) {
91+ const queryStr = JSON . stringify ( req . query , null , 2 )
92+ const truncatedQuery = this . truncateForSlack ( queryStr , 2700 )
93+ sections . push ( {
94+ title : 'Query Params' ,
95+ text : `\`\`\`${ truncatedQuery } \`\`\`` ,
96+ } )
97+ }
98+
99+ // Stack trace section
100+ if ( error ?. stack ) {
101+ const truncatedStack = this . truncateForSlack ( error . stack , 2700 )
102+ sections . push ( {
103+ title : 'Stack Trace' ,
104+ text : `\`\`\`${ truncatedStack } \`\`\`` ,
105+ } )
106+ }
107+
108+ sendSlackNotification (
109+ SlackChannel . ALERTS ,
110+ SlackPersona . ERROR_REPORTER ,
111+ `API Error ${ code } : ${ req . method } ${ req . url } ` ,
112+ sections ,
113+ )
114+ }
115+
24116 async error ( req , res , error ) {
25117 if ( error && error . name && error . name . includes ( 'Sequelize' ) ) {
26118 req . log . error (
@@ -35,6 +127,10 @@ export default class ApiResponseHandler extends LoggerBase {
35127 } ,
36128 'Database error while processing REST API request!' ,
37129 )
130+ this . sendServerErrorToSlack ( req , error , 500 , {
131+ sql : error . sql ,
132+ dbErrorMessage : error . original ?. message ,
133+ } )
38134 res . status ( 500 ) . send ( 'Internal Server Error' )
39135 } else if ( error && [ 400 , 401 , 403 , 404 ] . includes ( error . code ) ) {
40136 req . log . error (
@@ -57,6 +153,12 @@ export default class ApiResponseHandler extends LoggerBase {
57153 { code : error . code , url : req . url , method : req . method , query : req . query , body : req . body } ,
58154 'Error while processing REST API request!' ,
59155 )
156+
157+ // Send Slack notification for server errors (500-599)
158+ if ( error . code >= 500 && error . code <= 599 ) {
159+ this . sendServerErrorToSlack ( req , error , error . code )
160+ }
161+
60162 res . status ( error . code ) . send ( error . message )
61163 }
62164 }
0 commit comments