Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 44 additions & 29 deletions src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,24 @@ server.register(compression, {
server.setErrorHandler((error, request, reply) => {
const alreadySent = reply.sent || reply.raw.headersSent || reply.raw.writableEnded;
const errorCode = error.statusCode || 500;
const errorLog = {
reqId: request.id,
correlationId: request.correlationId,
url: request.url,
method: request.method,
statusCode: errorCode,
error: error.message,
stack: error.stack
};
request.log.error(errorLog);
if(error.stack){
const wrappedError = new Error(error.message);
wrappedError.stack = error.stack;
// For `request.log.error`, you can pass either:
// 1. (errorMessageStr) — logs the error string if Error is not provided, or
// 2. (Error) — logs the error object with its stack trace, or
// 3. (Error, message) — logs both the structured error and a custom message.
// This ensures proper parsing and correlation in the log pipeline.
request.log.error(wrappedError, `Request error: ${error.message}`);
} else {
// ⚠️ If the error is not an instance of Error, log it as a plain string.
// When the first argument is a string, any subsequent arguments are ignored.
// For consistency:
// - `request.log.error`: use either (Error, message) or a single string
// - `request.log.info`: only supports a single string argument
// These rules ensure correct log formatting and compatibility with our dashboards.
request.log.error(`Request error: ${error.message}`);
}
if(alreadySent){
// the api already set the appropriate error message. we shouldnt do anything now.
return;
Expand Down Expand Up @@ -85,11 +93,17 @@ server.addHook('onRequest', (request, reply, done) => {

request.startTime = Date.now();
if (request.headers['content-length']) {
// For request.log.info, Pino supports either a plain string or a JSON object with a `message` field.
// Use a string message for most info logs — the request ID is automatically included in the dashboard.
// Only use a JSON object when you need to attach extra metadata (e.g., correlationId, URL, size, etc.).
// These fields are optional since you can always look up related context by request ID in the dashboard.
request.log.info({
message: 'Request size',
reqId: request.id,
url: request.url,
correlationId: request.correlationId,
size: `${request.headers['content-length']} bytes`
}, 'Request size');
});
}
done();
});
Expand All @@ -109,31 +123,27 @@ server.addHook('onRequest', (request, reply, done) => {
});
if (!routeExists) {
request.log.error({
message: 'Route not found',
reqId: request.id,
correlationId: request.correlationId,
url: sanitizedUrl,
method: request.method,
ip: request.ips
}, "Route not found");
});
reply.code(HTTP_STATUS_CODES.NOT_FOUND);
return reply.send({error: 'Not Found'});
} else if (!isAuthenticated(request)) {
request.log.warn({
message: 'Unauthorized access attempt',
reqId: request.id,
correlationId: request.correlationId,
url: sanitizedUrl,
method: request.method,
ip: request.ips
}, "Unauthorized access attempt");
});
reply.code(HTTP_STATUS_CODES.UNAUTHORIZED);
return reply.send({error: 'Unauthorized'});
}
request.log.info({
reqId: request.id,
correlationId: request.correlationId,
url: sanitizedUrl,
method: request.method
}, "Request authenticated");
done();
});

Expand All @@ -153,13 +163,14 @@ server.addHook('onRequest', (request, reply, done) => {
server.addHook('onResponse', (request, reply, done) => {
const duration = Date.now() - request.startTime;
request.log.info({
message: 'Request completed',
reqId: request.id,
correlationId: request.correlationId,
url: request.url,
method: request.method,
statusCode: reply.statusCode,
duration: `${duration}ms`
}, 'Request completed');
});
done();
});

Expand Down Expand Up @@ -264,9 +275,9 @@ export async function startServer() {
port: configs.port,
host: configs.allowPublicAccess ? '0.0.0.0' : 'localhost'
});
server.log.info({message: `Server started on port ${configs.port}`});
server.log.info(`Server started on port ${configs.port}`);
} catch (err) {
server.log.error({message: 'Error starting server', error: err});
server.log.error(err, 'Error starting server');
process.exit(1);
}
}
Expand All @@ -278,41 +289,45 @@ export async function close() {
server.log.info({message: 'Shutting down server...'});
try {
const shutdownTimeout = setTimeout(() => {
server.log.error({message: 'Forced shutdown after timeout'});
server.log.error('Forced shutdown after timeout');
process.exit(1);
}, CLEANUP_GRACE_TIME_5SEC);

await server.close();
clearTimeout(shutdownTimeout);
server.log.info({message: 'Server shut down successfully'});
server.log.info('Server shut down successfully');
} catch (err) {
server.log.error({message: 'Error during shutdown', error: err});
server.log.error(err, 'Error during shutdown');
process.exit(1);
}
}

// Handle process termination
process.on('SIGTERM', async () => {
server.log.info({message: 'SIGTERM received'});
server.log.info('SIGTERM received');
await close();
process.exit(0);
});

process.on('SIGINT', async () => {
server.log.info({message: 'SIGINT received'});
server.log.info('SIGINT received');
await close();
process.exit(0);
});

// Handle uncaught exceptions
process.on('uncaughtException', (err) => {
server.log.error({message: 'Uncaught Exception', error: err});
server.log.error(err, 'Uncaught Exception');
close().then(() => process.exit(1));
});

// Handle unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
// non error rejections do not have a stack trace and hence cannot be located.
server.log.error({message: 'Unhandled Rejection at promise', error: reason instanceof Error ? reason : { reason }, promise});
if(reason instanceof Error){
server.log.error(reason, 'Unhandled Rejection at promise');
} else {
server.log.error('Unhandled Rejection at promise. reason:' + reason);
}
close().then(() => process.exit(1));
});
Loading