Pipe Next js Logs to Slack Webhook
logger.tsTypeScriptroute.tsTypeScript
logger.ts
type LogType = "log" | "warn" | "error";
/**
* Send logs to the /api/log endpoint.
* @param type - The type of log (log, warn, error)
* @param messages - The log messages
*/
const sendToApiLog = async (
type: LogType,
messages: unknown[]
): Promise<void> => {
const formattedMessages = messages.map((msg) =>
typeof msg === "object" ? JSON.stringify(msg, null, 2) : String(msg)
);
try {
const response = await fetch("/api/log", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
type,
messages: formattedMessages,
source: "Next.js",
}),
});
if (!response.ok) {
console.error(`Failed to send log to API: ${await response.text()}`);
}
} catch (err) {
console.error("Error sending log to API:", err);
}
};
// Override console methods
const originalConsoleLog = console.log;
const originalConsoleWarn = console.warn;
const originalConsoleError = console.error;
if (process.env.NODE_ENV === "production") {
console.log = (...args: unknown[]) => {
sendToApiLog("log", args);
};
console.warn = (...args: unknown[]) => {
sendToApiLog("warn", args);
};
console.error = (...args: unknown[]) => {
sendToApiLog("error", args);
};
} else {
// In development, keep the original console methods
console.log = (...args: unknown[]) => {
originalConsoleLog(...args);
};
console.warn = (...args: unknown[]) => {
originalConsoleWarn(...args);
};
console.error = (...args: unknown[]) => {
originalConsoleError(...args);
};
}
route.ts
import { NextResponse } from "next/server";
import { env } from "~/env";
export async function POST(request: Request) {
try {
const { messages, source, type } = await request.json();
const slackLogsWebhookUrl = env.SLACK_LOGS_WEBHOOK_URL;
const slackErrorsWebhookUrl = env.SLACK_ERRORS_WEBHOOK_URL;
if (!slackLogsWebhookUrl || !slackErrorsWebhookUrl) {
throw new Error("Slack webhook URLs are not configured.");
}
let webhookUrl: string;
let prefix: string;
switch (type) {
case "error":
webhookUrl = slackErrorsWebhookUrl;
prefix = "🚨 *ERROR*";
break;
case "warn":
webhookUrl = slackLogsWebhookUrl;
prefix = "⚠️ *WARN*";
break;
default:
webhookUrl = slackLogsWebhookUrl;
prefix = "📝 *LOG*";
}
const formattedMessage = `${prefix} - *[${source}]*\n${messages.join(
"\n"
)}`;
const slackResponse = await fetch(webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: formattedMessage }),
});
if (!slackResponse.ok) {
throw new Error(`Slack webhook failed: ${await slackResponse.text()}`);
}
return NextResponse.json({ success: true }, { status: 200 });
} catch (err) {
console.error("Error in report-error route:", err);
return NextResponse.json(
{ success: false, error: "Error processing request" },
{ status: 500 }
);
}
}
Updated: 11/22/2024