Sentry.startTransaction

This commit is contained in:
2025-03-29 18:42:41 +01:00
parent 1c6802995f
commit bab3b59750

View File

@@ -37,14 +37,12 @@ router.get('/', (req, res) => {
return res.status(403).json({ success: false, error: 'Operations on private or local IP addresses are not allowed.' }); return res.status(403).json({ success: false, error: 'Operations on private or local IP addresses are not allowed.' });
} }
const transaction = Sentry.startTransaction({ // Get the transaction created by Sentry's tracingHandler middleware
op: "traceroute.stream", const scope = Sentry.getCurrentScope();
name: `/api/traceroute?targetIp=${targetIp}`, const transaction = scope?.getTransaction();
}); // Add specific context for this request if needed
Sentry.configureScope(scope => { scope?.setContext("traceroute_details", { targetIp: targetIp });
scope.setSpan(transaction);
scope.setContext("request", { ip: requestIp, targetIp });
});
try { try {
logger.info({ requestIp, targetIp }, `Starting traceroute stream...`); logger.info({ requestIp, targetIp }, `Starting traceroute stream...`);
@@ -61,10 +59,21 @@ router.get('/', (req, res) => {
let buffer = ''; let buffer = '';
// Flag to ensure transaction is only finished once
let transactionFinished = false;
const finishTransaction = (status) => {
if (!transactionFinished && transaction) {
transaction.setStatus(status);
transaction.finish();
transactionFinished = true;
logger.debug({ requestIp, targetIp, status }, 'Sentry transaction finished.');
}
};
const sendEvent = (event, data) => { const sendEvent = (event, data) => {
try { try {
if (!res.writableEnded) { if (!res.writableEnded) {
// Ensure error events always have a string in data.error
if (event === 'error' && (!data || typeof data.error !== 'string')) { if (event === 'error' && (!data || typeof data.error !== 'string')) {
const safeErrorMessage = getErrorMessage(data?.error, 'Traceroute encountered an unspecified error.'); const safeErrorMessage = getErrorMessage(data?.error, 'Traceroute encountered an unspecified error.');
logger.warn({ requestIp, targetIp, originalData: data }, `Corrected invalid error event data. Sending: ${safeErrorMessage}`); logger.warn({ requestIp, targetIp, originalData: data }, `Corrected invalid error event data. Sending: ${safeErrorMessage}`);
@@ -75,12 +84,13 @@ router.get('/', (req, res) => {
logger.warn({ requestIp, targetIp, event }, "Attempted to write to closed SSE stream."); logger.warn({ requestIp, targetIp, event }, "Attempted to write to closed SSE stream.");
} }
} catch (e) { } catch (e) {
// This catch handles errors during res.write, likely client disconnect
logger.error({ requestIp, targetIp, event, error: e.message }, "Error writing to SSE stream (client likely disconnected)"); logger.error({ requestIp, targetIp, event, error: e.message }, "Error writing to SSE stream (client likely disconnected)");
Sentry.captureException(e, { level: 'warning', extra: { requestIp, targetIp, event } }); Sentry.captureException(e, { level: 'warning', extra: { requestIp, targetIp, event } });
if (proc && !proc.killed) proc.kill(); if (proc && !proc.killed) proc.kill();
if (!res.writableEnded) res.end(); if (!res.writableEnded) res.end();
transaction.setStatus('internal_error'); // Finish transaction here as well, as the request handling failed
transaction.finish(); finishTransaction('internal_error');
} }
}; };
@@ -91,10 +101,10 @@ router.get('/', (req, res) => {
lines.forEach(line => { lines.forEach(line => {
const parsed = parseTracerouteLine(line); const parsed = parseTracerouteLine(line);
if (parsed) { if (parsed) {
logger.debug({ requestIp, targetIp, hop: parsed.hop, ip: parsed.ip }, 'Sending hop data'); // logger.debug({ requestIp, targetIp, hop: parsed.hop, ip: parsed.ip }, 'Sending hop data');
sendEvent('hop', parsed); sendEvent('hop', parsed);
} else if (line.trim()) { } else if (line.trim()) {
logger.debug({ requestIp, targetIp, message: line.trim() }, 'Sending info data'); // logger.debug({ requestIp, targetIp, message: line.trim() }, 'Sending info data');
sendEvent('info', { message: line.trim() }); sendEvent('info', { message: line.trim() });
} }
}); });
@@ -104,17 +114,16 @@ router.get('/', (req, res) => {
const errorMsg = getErrorMessage(data.toString().trim(), 'Traceroute produced unknown stderr output.'); const errorMsg = getErrorMessage(data.toString().trim(), 'Traceroute produced unknown stderr output.');
logger.warn({ requestIp, targetIp, stderr: errorMsg }, 'Traceroute stderr output'); logger.warn({ requestIp, targetIp, stderr: errorMsg }, 'Traceroute stderr output');
Sentry.captureMessage('Traceroute stderr output', { level: 'warning', extra: { requestIp, targetIp, stderr: errorMsg } }); Sentry.captureMessage('Traceroute stderr output', { level: 'warning', extra: { requestIp, targetIp, stderr: errorMsg } });
sendEvent('error', { error: errorMsg }); // errorMsg is now guaranteed to be a string sendEvent('error', { error: errorMsg });
}); });
proc.on('error', (err) => { proc.on('error', (err) => {
const errorMsg = getErrorMessage(err, 'Failed to start traceroute command due to an unknown error.'); const errorMsg = getErrorMessage(err, 'Failed to start traceroute command due to an unknown error.');
logger.error({ requestIp, targetIp, error: errorMsg }, `Failed to start traceroute command`); logger.error({ requestIp, targetIp, error: errorMsg }, `Failed to start traceroute command`);
Sentry.captureException(err, { extra: { requestIp, targetIp } }); // Send original error to Sentry Sentry.captureException(err, { extra: { requestIp, targetIp } });
sendEvent('error', { error: `Failed to start traceroute: ${errorMsg}` }); // Send safe message to client sendEvent('error', { error: `Failed to start traceroute: ${errorMsg}` });
if (!res.writableEnded) res.end(); if (!res.writableEnded) res.end();
transaction.setStatus('internal_error'); finishTransaction('internal_error'); // Finish transaction on process error
transaction.finish();
}); });
proc.on('close', (code) => { proc.on('close', (code) => {
@@ -124,42 +133,41 @@ router.get('/', (req, res) => {
else if (buffer.trim()) sendEvent('info', { message: buffer.trim() }); else if (buffer.trim()) sendEvent('info', { message: buffer.trim() });
} }
let status = 'ok';
if (code !== 0) { if (code !== 0) {
const errorMsg = `Traceroute command failed with exit code ${code}`; const errorMsg = `Traceroute command failed with exit code ${code}`;
logger.error({ requestIp, targetIp, exitCode: code }, errorMsg); logger.error({ requestIp, targetIp, exitCode: code }, errorMsg);
Sentry.captureMessage('Traceroute command failed', { level: 'error', extra: { requestIp, targetIp, exitCode: code } }); Sentry.captureMessage('Traceroute command failed', { level: 'error', extra: { requestIp, targetIp, exitCode: code } });
sendEvent('error', { error: errorMsg }); // Send specific error message sendEvent('error', { error: errorMsg });
transaction.setStatus('unknown_error'); status = 'unknown_error';
} else { } else {
logger.info({ requestIp, targetIp }, `Traceroute stream completed successfully.`); logger.info({ requestIp, targetIp }, `Traceroute stream completed successfully.`);
transaction.setStatus('ok');
} }
sendEvent('end', { exitCode: code }); sendEvent('end', { exitCode: code });
if (!res.writableEnded) res.end(); if (!res.writableEnded) res.end();
transaction.finish(); finishTransaction(status); // Finish transaction on process close
}); });
req.on('close', () => { req.on('close', () => {
logger.info({ requestIp, targetIp }, 'Client disconnected from traceroute stream, killing process.'); logger.info({ requestIp, targetIp }, 'Client disconnected from traceroute stream, killing process.');
if (proc && !proc.killed) proc.kill(); if (proc && !proc.killed) proc.kill();
if (!res.writableEnded) res.end(); if (!res.writableEnded) res.end();
transaction.setStatus('cancelled'); finishTransaction('cancelled'); // Finish transaction on client disconnect
transaction.finish();
}); });
} catch (error) { } catch (error) {
// This catch handles errors during the initial setup (e.g., spawn fails immediately)
const errorMsg = getErrorMessage(error, 'Failed to initiate traceroute due to an internal server error.'); const errorMsg = getErrorMessage(error, 'Failed to initiate traceroute due to an internal server error.');
logger.error({ requestIp, targetIp, error: errorMsg, stack: error.stack }, 'Error setting up traceroute stream'); logger.error({ requestIp, targetIp, error: errorMsg, stack: error.stack }, 'Error setting up traceroute stream');
Sentry.captureException(error, { extra: { requestIp, targetIp } }); // Send original error to Sentry Sentry.captureException(error, { extra: { requestIp, targetIp } });
transaction.setStatus('internal_error'); // Finish the transaction if an error occurs during setup
transaction.finish(); finishTransaction('internal_error');
if (!res.headersSent) { if (!res.headersSent) {
res.status(500).json({ success: false, error: `Failed to initiate traceroute: ${errorMsg}` }); res.status(500).json({ success: false, error: `Failed to initiate traceroute: ${errorMsg}` });
} else { } else {
try { try {
if (!res.writableEnded) { if (!res.writableEnded) {
// Use the safe sendEvent function here as well
sendEvent('error', { error: `Internal server error during setup: ${errorMsg}` }); sendEvent('error', { error: `Internal server error during setup: ${errorMsg}` });
res.end(); res.end();
} }