quotely / app.js
Ditzzy AF
feat(app): remove headers logging from request middleware
c3df263
Raw
History Blame Contribute Delete
10.1 kB
const logger = require("koa-logger");
const responseTime = require("koa-response-time");
const bodyParser = require("koa-bodyparser");
const Router = require("koa-router");
const Koa = require("koa");
const os = require("os");
const fs = require("fs");
const https = require("https");
const path = require("path");
const app = new Koa();
// Daftar assets yang perlu didownload
const requiredAssets = [
{
url: "https://raw.githubusercontent.com/LyoSU/quote-api/master/assets/pattern_02.png",
dest: "assets/pattern_02.png",
},
{
url: "https://raw.githubusercontent.com/LyoSU/quote-api/master/assets/pattern_ny.png",
dest: "assets/pattern_ny.png",
},
{
url: "https://raw.githubusercontent.com/LyoSU/quote-api/master/assets/pattern_ny_old.png",
dest: "assets/pattern_ny_old.png",
},
{
url: "https://raw.githubusercontent.com/LyoSU/quote-api/master/assets/emoji/emoji-apple-image.json",
dest: "assets/emoji/emoji-apple-image.json",
},
{
url: "https://raw.githubusercontent.com/LyoSU/quote-api/master/assets/emoji/emoji-google-image.json",
dest: "assets/emoji/emoji-google-image.json",
},
{
url: "https://raw.githubusercontent.com/LyoSU/quote-api/master/assets/emoji/emoji-joypixels-image.json",
dest: "assets/emoji/emoji-joypixels-image.json",
},
{
url: "https://raw.githubusercontent.com/LyoSU/quote-api/master/assets/emoji/emoji-twitter-image.json",
dest: "assets/emoji/emoji-twitter-image.json",
},
];
// Fungsi untuk download file
function downloadFile(url, dest) {
return new Promise((resolve, reject) => {
const dir = path.dirname(dest);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
if (fs.existsSync(dest)) {
console.log(`βœ“ ${dest} already exists`);
return resolve();
}
const file = fs.createWriteStream(dest);
console.log(`⏬ Downloading ${path.basename(dest)}...`);
https
.get(url, (response) => {
if (response.statusCode === 301 || response.statusCode === 302) {
return downloadFile(response.headers.location, dest)
.then(resolve)
.catch(reject);
}
if (response.statusCode !== 200) {
reject(
new Error(`Failed to download ${url}: ${response.statusCode}`)
);
return;
}
response.pipe(file);
file.on("finish", () => {
file.close();
console.log(`βœ… Downloaded ${dest}`);
resolve();
});
})
.on("error", (err) => {
fs.unlink(dest, () => {});
reject(err);
});
file.on("error", (err) => {
fs.unlink(dest, () => {});
reject(err);
});
});
}
// Download semua assets yang diperlukan
async function ensureAssets() {
console.log("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.log("πŸ” Checking required assets...");
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
try {
for (const asset of requiredAssets) {
await downloadFile(asset.url, asset.dest);
}
console.log("\n✨ All assets ready!\n");
} catch (err) {
console.error("\n❌ Error downloading assets:");
console.error(err.message);
throw err;
}
}
// Custom logger untuk melihat request body dan error detail
app.use(async (ctx, next) => {
const start = Date.now();
// Log request
console.log("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.log("πŸ“¨ INCOMING REQUEST");
console.log("Time:", new Date().toISOString());
console.log("Method:", ctx.method);
console.log("URL:", ctx.url);
if (ctx.request.body && Object.keys(ctx.request.body).length > 0) {
console.log("Body:", JSON.stringify(ctx.request.body, null, 2));
}
try {
await next();
// Log successful response
const ms = Date.now() - start;
console.log("\nβœ… RESPONSE SUCCESS");
console.log("Status:", ctx.status);
console.log("Time:", ms + "ms");
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
} catch (err) {
const ms = Date.now() - start;
// Log error dengan detail lengkap
console.log("\n❌ ERROR OCCURRED");
console.log("Time:", new Date().toISOString());
console.log("Duration:", ms + "ms");
console.log("Method:", ctx.method);
console.log("URL:", ctx.url);
console.log("Status:", err.status || err.statusCode || 500);
console.log("Error Name:", err.name);
console.log("Error Message:", err.message);
if (err.errors) {
console.log("Validation Errors:", JSON.stringify(err.errors, null, 2));
}
console.log("\nπŸ“š STACK TRACE:");
console.log(err.stack);
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
// Set response untuk client
ctx.status = err.status || err.statusCode || 500;
ctx.body = {
success: false,
error: {
message: err.message || "Internal Server Error",
status: ctx.status,
timestamp: new Date().toISOString(),
...(err.errors && { details: err.errors }),
},
};
// Emit error event
ctx.app.emit("error", err, ctx);
}
});
app.use(logger());
app.use(responseTime());
app.use(
bodyParser({
onerror: (err, ctx) => {
console.error("\n⚠️ BODY PARSER ERROR");
console.error("Message:", err.message);
console.error("Stack:", err.stack);
console.error("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
ctx.throw(422, "Invalid JSON body");
},
})
);
// Tambahkan middleware untuk log response body pada error
app.use(async (ctx, next) => {
await next();
// Log response body jika status error (4xx atau 5xx)
if (ctx.status >= 400) {
console.log("\n⚠️ ERROR RESPONSE BODY:");
console.log(JSON.stringify(ctx.body, null, 2));
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
}
});
app.use(require("./helpers").helpersApi);
const route = new Router();
const routes = require("./routes");
// Helper untuk format memory
function formatBytes(bytes) {
const sizes = ["B", "KB", "MB", "GB", "TB"];
if (bytes === 0) return "0 B";
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${sizes[i]}`;
}
function formatUptime(seconds) {
const days = Math.floor(seconds / (24 * 3600));
seconds %= 24 * 3600;
const hours = Math.floor(seconds / 3600);
seconds %= 3600;
const minutes = Math.floor(seconds / 60);
seconds = Math.floor(seconds % 60);
const parts = [];
if (days) parts.push(`${days} day${days > 1 ? "s" : ""}`);
if (hours) parts.push(`${hours} hour${hours > 1 ? "s" : ""}`);
if (minutes) parts.push(`${minutes} minute${minutes > 1 ? "s" : ""}`);
if (seconds || parts.length === 0)
parts.push(`${seconds} second${seconds > 1 ? "s" : ""}`);
return parts.join(" ");
}
route.get(["/", "/ping"], async (ctx) => {
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;
const usagePercent = ((usedMem / totalMem) * 100).toFixed(2);
const info = {
hostname: os.hostname(),
platform: os.platform(),
arch: os.arch(),
uptime: formatUptime(os.uptime()),
cpu: {
model: os.cpus()[0].model,
cores: os.cpus().length,
},
memory: {
total: formatBytes(totalMem),
used: formatBytes(usedMem),
free: formatBytes(freeMem),
usage: `${usagePercent}%`,
},
};
ctx.type = "application/json";
ctx.body = JSON.stringify(info, null, 2);
});
route.use("/*", routes.routeApi.routes());
app.use(route.routes());
// Global error event listener
app.on("error", (err, ctx) => {
// Additional logging bisa ditambahkan di sini
// Misal: kirim ke Sentry, CloudWatch, dll
if (process.env.DEBUG) {
console.log("πŸ” Additional error context:", {
url: ctx.url,
method: ctx.method,
status: ctx.status,
headers: ctx.headers,
});
}
});
const port = process.env.PORT || 7860;
// Start server dengan download assets dulu
async function startServer() {
try {
// Download assets dulu sebelum start server
await ensureAssets();
// Baru start server
app.listen(port, () => {
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.log("βœ… Server started successfully");
console.log("πŸ“ Listening on localhost, port", port);
console.log("🌐 Health check: http://localhost:" + port + "/ping");
console.log("πŸ› Debug mode:", process.env.DEBUG ? "ON" : "OFF");
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
});
} catch (err) {
console.error("❌ Failed to start server:", err.message);
process.exit(1);
}
}
// Jalankan server
startServer();
// Handle unhandled promise rejections
process.on("unhandledRejection", (reason, promise) => {
console.error("\nπŸ”₯ Unhandled Rejection at:", promise);
console.error("Reason:", reason);
console.error("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
});
// Handle uncaught exceptions
process.on("uncaughtException", (err) => {
console.error("\nπŸ’₯ Uncaught Exception:");
console.error(err);
console.error("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
process.exit(1);
});