Enable Sentry.logger API (enableLogs, beforeSendLog) and add logInfo/logWarn/logError helpers. Bump @sentry/node ^9.47.1 → ^10.39.0, @sentry/profiling-node → ^10.39.0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
132 lines
3.8 KiB
TypeScript
132 lines
3.8 KiB
TypeScript
/**
|
|
* Sentry SDK integration for MCP servers.
|
|
*
|
|
* Provides error tracking and performance monitoring for MCP protocol tools
|
|
* while filtering out normal error responses (isError: true).
|
|
*
|
|
* Usage:
|
|
* import { initSentry } from "./sentry.js";
|
|
*
|
|
* initSentry(process.env.ENVIRONMENT || "development");
|
|
*/
|
|
|
|
import * as Sentry from "@sentry/node";
|
|
// Profiling integration removed due to type incompatibilities (CF-567)
|
|
|
|
export function initSentry(environment: string = "development"): void {
|
|
const dsn = process.env.SENTRY_DSN || "";
|
|
|
|
if (!dsn) {
|
|
console.error("SENTRY_DSN not set, Sentry disabled");
|
|
return;
|
|
}
|
|
|
|
Sentry.init({
|
|
dsn,
|
|
environment,
|
|
tracesSampleRate: parseFloat(process.env.SENTRY_TRACE_SAMPLE_RATE || "0.1"),
|
|
profilesSampleRate: parseFloat(
|
|
process.env.SENTRY_PROFILE_SAMPLE_RATE || "0.01"
|
|
),
|
|
integrations: [
|
|
Sentry.httpIntegration(),
|
|
Sentry.postgresIntegration(),
|
|
],
|
|
beforeSend(event: Sentry.ErrorEvent, hint: Sentry.EventHint) {
|
|
// MCP protocol: Don't send normal error responses (isError: true)
|
|
const originalException = hint.originalException as any;
|
|
if (originalException?.isError === true) {
|
|
return null; // This is a normal MCP error response, not an exception
|
|
}
|
|
|
|
// Strip PII from headers
|
|
if (event.request?.headers) {
|
|
const headers = { ...event.request.headers };
|
|
delete headers.Authorization;
|
|
delete headers.Cookie;
|
|
delete headers["X-API-Key"];
|
|
event.request.headers = headers;
|
|
}
|
|
|
|
// Redact sensitive data in request body
|
|
if (event.request?.data) {
|
|
let dataStr = JSON.stringify(event.request.data);
|
|
// Redact API keys
|
|
dataStr = dataStr.replace(
|
|
/(api[_-]?key|token|secret)["']?\s*[:=]\s*["']?[\w-]+/gi,
|
|
"$1=REDACTED"
|
|
);
|
|
// Redact emails
|
|
dataStr = dataStr.replace(
|
|
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
|
|
"EMAIL_REDACTED"
|
|
);
|
|
try {
|
|
event.request.data = JSON.parse(dataStr);
|
|
} catch {
|
|
event.request.data = dataStr;
|
|
}
|
|
}
|
|
|
|
return event;
|
|
},
|
|
enableLogs: true,
|
|
beforeSendLog(log) {
|
|
if (log.level === "debug") return null;
|
|
return log;
|
|
},
|
|
maxBreadcrumbs: 30,
|
|
attachStacktrace: true,
|
|
release: process.env.APP_VERSION || "unknown",
|
|
sendDefaultPii: false, // Never send PII automatically
|
|
});
|
|
|
|
console.log(
|
|
`✅ Sentry initialized: ${environment} - ${process.env.APP_VERSION || "unknown"}`
|
|
);
|
|
}
|
|
|
|
export function logInfo(msg: string, data?: Record<string, unknown>): void {
|
|
Sentry.logger.info(msg, data);
|
|
}
|
|
|
|
export function logWarn(msg: string, data?: Record<string, unknown>): void {
|
|
Sentry.logger.warn(msg, data);
|
|
}
|
|
|
|
export function logError(msg: string, data?: Record<string, unknown>): void {
|
|
Sentry.logger.error(msg, data);
|
|
}
|
|
|
|
/**
|
|
* Wrap MCP tool handler with Sentry transaction tracking.
|
|
*
|
|
* Usage:
|
|
* const result = await withSentryTransaction("tool_name", async () => {
|
|
* return await performToolOperation();
|
|
* });
|
|
*/
|
|
export async function withSentryTransaction<T>(
|
|
toolName: string,
|
|
handler: () => Promise<T>
|
|
): Promise<T> {
|
|
return Sentry.startSpan(
|
|
{ name: `tool_${toolName}`, op: "mcp.tool" },
|
|
async (span: Sentry.Span) => {
|
|
try {
|
|
const result = await handler();
|
|
span.setStatus({ code: 1, message: "ok" }); // SpanStatusCode.OK
|
|
return result;
|
|
} catch (error: unknown) {
|
|
// Capture exception to Sentry (unless it's a normal MCP error)
|
|
const err = error as { isError?: boolean };
|
|
if (!err.isError) {
|
|
Sentry.captureException(error);
|
|
}
|
|
span.setStatus({ code: 2, message: "error" }); // SpanStatusCode.ERROR
|
|
throw error;
|
|
}
|
|
}
|
|
);
|
|
}
|