Skip to content

Commit

Permalink
Add final exception handler to prevent container crashes (#629)
Browse files Browse the repository at this point in the history
  • Loading branch information
sorenjohanson authored Dec 19, 2024
1 parent a95b422 commit 3e55b61
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 0 deletions.
62 changes: 62 additions & 0 deletions teammapper-backend/src/filters/global-exception.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { ExceptionFilter, Catch, ArgumentsHost, Logger, HttpException } from '@nestjs/common';

// This is for any unhandled gateway and "internal" NestJS related errors - like if the gateway can't reach clients or things like that.
// It will try to always keep clients and their websockets alive and gracefully send errors over the wire, without revealing internal error reasons.
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(GlobalExceptionFilter.name);

catch(exception: Error | HttpException | unknown, host: ArgumentsHost) {
const ctx = host.getType();

this.logger.error({
error: exception,
type: exception?.constructor?.name || typeof exception,
message: exception?.message || 'Unknown error',
stack: exception?.stack,
context: ctx,
});

try {
switch (ctx) {
case 'http': {
const response = host.switchToHttp().getResponse();
return response.status(500).json({
statusCode: 500,
message: 'Internal server error',
timestamp: new Date().toISOString(),
});
}

case 'ws': {
const client = host.switchToWs().getClient();
const error = {
event: 'error',
data: {
message: 'Internal server error',
timestamp: new Date().toISOString(),
},
};

if (typeof client.emit === 'function') {
client.emit('error', error);
} else if (typeof client.send === 'function') {
client.send(JSON.stringify(error));
}
break;
}

default: {
// Handle any runtime errors outside HTTP/WS contexts
this.logger.error(`Unhandled exception type: ${ctx}`);
// Emit to process handler as last resort
process.emit('uncaughtException', exception);
}
}
} catch (handlerError) {
// If the error handler itself fails, log it and emit to process
this.logger.error('Global exception handler failed: ', handlerError);
process.emit('uncaughtException', exception);
}
}
}
17 changes: 17 additions & 0 deletions teammapper-backend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,29 @@ import { NestFactory } from '@nestjs/core'
import AppModule from './app.module'
import configService from './config.service'
import { createProxyMiddleware } from 'http-proxy-middleware'
import { GlobalExceptionFilter } from './filters/global-exception.filter';
import { Logger } from '@nestjs/common'

async function bootstrap() {
const logger = new Logger('Main Process');

// Process-level handlers for uncaught errors - anything that happens outside of NestJS, such as type errors.
// This is only logged server-side so we log the whole stack for better review.
process.on('uncaughtException', (error) => {
logger.error('Uncaught Exception: ', error.stack);
});

process.on('unhandledRejection', (reason: unknown) => {
const stack = reason instanceof Error ? reason.stack : 'No stack trace available';
logger.error('Unhandled Rejection. Stack trace: ', stack);
});

const app = await NestFactory.create(AppModule, {
logger: ['log', 'error', 'warn', 'debug'],
})

app.useGlobalFilters(new GlobalExceptionFilter());

app.use(
'/arasaac/api',
createProxyMiddleware({
Expand Down

0 comments on commit 3e55b61

Please sign in to comment.