ทำให้บริการของคุณสังเกตการณ์ได้และหยุดได้อย่างสะอาด ด้วยล็อกแบบ JSON โพรบ liveness/readiness และการจัดการ SIGTERM
เปิดบทเรียนนี้ใน Kodokonในระบบจริง ล็อกมีไว้สำหรับเครื่องจักร (Loki, Datadog, CloudWatch) ไม่ใช่มนุษย์ จงส่งออก หนึ่งอ็อบเจกต์ JSON ต่อหนึ่งบรรทัด (หนึ่งบรรทัด = หนึ่งอีเวนต์) พร้อมระดับ (level), เวลาแบบ ISO และฟิลด์ที่มีโครงสร้าง อย่าใช้การแทรกข้อความแบบอิสระที่ค้นหาไม่ได้เด็ดขาด รายละเอียดที่น้อยคนรู้ คือการเขียนไปยัง stdout เป็นแบบ ซิงโครนัส เมื่อปลายทางเป็นไฟล์หรือเทอร์มินัล แต่เป็นแบบอะซิงโครนัสเมื่อปลายทางเป็น pipe ดังนั้นล็อกเกอร์ที่พูดมากอาจบล็อกอีเวนต์ลูปได้ขึ้นอยู่กับปลายทาง นั่นคือเหตุผลทั้งหมดของไลบรารีอย่าง pino ที่ serialize ได้เร็วและสามารถถ่ายโอนภาระการเขียนออกไปได้
function log(level, message, fields = {}) {
const entry = {
level,
message,
time: new Date().toISOString(),
pid: process.pid,
...fields,
};
process.stdout.write(JSON.stringify(entry) + "\n");
}
log("info", "server started", { port: 3000 });
log("error", "db unreachable", { retryInMs: 5000 });จงแยกโพรบสองชนิดออกจากกัน Liveness ตอบคำถามว่า "ควรรีสตาร์ตโปรเซสหรือไม่?" มันต้องเรียบง่ายอยู่เสมอ โดยไม่พึ่งพาสิ่งภายนอกใด ๆ ส่วน Readiness ตอบคำถามว่า "ฉันรับทราฟฟิกได้หรือยัง?" มันอาจตรวจสอบฐานข้อมูล และเหนือสิ่งอื่นใดคือเปลี่ยนเป็น 503 ระหว่างการปิด เพื่อให้ load balancer ถอดอินสแตนซ์ออกก่อนที่มันจะดับ การสับสนระหว่างสองอย่างนี้ทำให้โปรเซสที่แข็งแรงถูกรีสตาร์ตทันทีที่สิ่งที่พึ่งพาสะดุด
import { createServer } from "node:http";
let ready = true;
const server = createServer((req, res) => {
if (req.url === "/healthz") {
res.writeHead(ready ? 200 : 503);
res.end(ready ? "ok" : "draining");
return;
}
res.end("hello");
});
server.listen(3000);
function shutdown() {
ready = false;
server.closeIdleConnections();
server.close(() => process.exit(0));
setTimeout(() => process.exit(1), 10_000).unref();
}
process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);ลำดับขั้นของการปิดอย่างนุ่มนวล คือ ตัวจัดการ (orchestrator) ส่ง SIGTERM คุณสลับ readiness เป็น 503 server.close() ปฏิเสธการเชื่อมต่อใหม่และรอให้คำขอที่กำลังดำเนินอยู่เสร็จสิ้น closeIdleConnections() ปิดการเชื่อมต่อ keep-alive ที่ว่างอยู่ ซึ่งมิฉะนั้นจะหน่วง close() ไว้ไม่มีที่สิ้นสุด ตัวจับเวลาที่มาร์กด้วย unref() ทำหน้าที่เป็นตาข่ายนิรภัย มันจะฆ่าโปรเซสหลังจาก 10 วินาที โดยตัวมันเองไม่ได้ขัดขวางไม่ให้โปรเซสออกก่อนหน้านั้น
process.on("uncaughtException", (err) => {
const entry = {
level: "fatal",
message: err.message,
stack: err.stack,
time: new Date().toISOString(),
};
process.stderr.write(JSON.stringify(entry) + "\n");
process.exit(1);
});
process.on("unhandledRejection", (reason) => {
throw reason;
});