노드에서는 예외처리가 정말 중요합니다. 예외란 보통 처리하지 못한 에러를 말합니다. 이러한 예외들은 실행 중인 노드 프로세스를 멈추게 만듭니다.
멀티 스레드 프로그램에서는 스레드 하나가 멈추면 그 일을 다른 스레드가 대신합니다. 하지만 노드의 메인 스레드는 하나뿐이므로 그 하나를 소중히 보호해야합니다. 메인 스레드가 에러로 인해 멈춘다는 것은 스레드를 갖고 있는 프로세스가 멈춘다는 뜻이고, 전체 서버도 멈춘다는 뜻과 같습니다. 아무리 신중을 기해 만들었다고 해도 항상 예기치 못한 에러는 발생하는 법입니다.
따라서 에러를 처리하는 방법을 익혀둬야 합니다. 에러 로그가 기록되더라도 작업은계속 진행 될 수 있도록 말입니다.
문법상의 에러는 없다고 사정하겠습니다. 실제 배포용 코두에 문법 에러가 있어서는 안 됩니다. 좋은 에디터를 사용하거나 좋은 문법 검사 툴을 사용해서 오탈자나 문법 에러가 발생하지 않도록 합니다.
다음 예제에서는 프로새스가 멈추지 않도록 에러를 잡아보겠습니다. 에러가 발생한 것 같은 부분을 try/catch문으로 감싸면 됩니다.
// error1.js
setInterval(() => {
console.log("시작");
try {
throw new Error("서버를 고장내주마!");
} catch (err) {
console.error(err);
}
}, 1000);
setInterval을 사용한 것은 프로세스가 멈추는지 여부를 체크하기 위해서입니다. 프로세스가 에러로 인해 멈추면 setInterval도 멈출 것입니다. setInterval 내부에 throw new Error()를 써서 에러를 강제로 발생시켰습니다.
$ node error1
시작
error: 서버를 고장내주마
...
시작
Error: 서버를 고장내주마!
...
// 계속반복
에러는 발생하지만 try/catch로 잡을 수 있고 setInterval도 직접 멈추기 전(Ctrl + C)까지 계속 실행됩니다. 이렇게 에러가 발생할 것 같은 부분을 미리 try/catch로 감싸면 됩니다.
이번에는 노드 자체에서 잡아주는 에러에 대해 알아보겠습니다.
//error2.js
const fs = require("fs");
setInterval(() => {
fs.unlink("./abcdefg.js", (err) => {
if (err) {
console.error(err);
}
});
}, 1000);
fs.unlink로 존재하지 않는 파일을 지우고 있습니다. 에러가 발샹하지만, 다행이 노드 내장 모듈의 에러는 실행 중인 프로세스를 멈추지 않습니다. 에러 로그를 기록해두고 나중에 원인을 찾아 수정하면 됩니다.
에러가 발생했을때 throw했습니다. 그런데 throw하면 노드 프로세스가 멈춰버립니다. 따라서 throw 하는 경우에는 반드시
try/catch문으로 throw한 에러를 잡아야합니다.
$ node error2
[Error: ENOENT: no such file or directory, unlink './abcdefg.js'] {
errno: -2,
code: 'ENOENT',
syscall: 'unlink',
path: './abcdefg.js'
}
[Error: ENOENT: no such file or directory, unlink './abcdefg.js'] {
errno: -2,
code: 'ENOENT',
syscall: 'unlink',
path: './abcdefg.js'
}
...
// 반복
노드 16버전부터 프로미스의 에러는 반드시 catch해야 합니다. catch하지 않으면 에러와 함께 노드 프로세스가 종료됩니다.
// error3.js
const fs = require("fs").promises;
setInterval(() => {
fs.unlink("./abcdefg.js").catch(console.error);
}, 1000);
$ node error3
[Error: ENOENT: no such file or directory, unlink './abcdefg.js'] {
errno: -2,
code: 'ENOENT',
syscall: 'unlink',
path: './abcdefg.js'
}
[Error: ENOENT: no such file or directory, unlink './abcdefg.js'] {
errno: -2,
code: 'ENOENT',
syscall: 'unlink',
path: './abcdefg.js'
}
....
//계속 반복
이번에는 정말 예측이 불가능한 에러를 처리하는 방법을 알아보겠습니다.
process.on("uncaughtException", (err) => {
console.error("예기치 못한 에러", err);
});
setInterval(() => {
throw new Error("서버를 고장내주마!");
}, 1000);
setTimeout(() => {
console.log("실행됩니다");
}, 2000);
process 객체에 uncaughtException 이벤트 리스너를 달았습니다. 처리하지 못한 에러가 발생했을 때 이벤트 리스너가 실행되고 프로세스가 유지됩니다. 이 부분이 없다면 위 예제에서는 setTimeout이 실행되지 않습니다. 실행 후 1초 만에 setInterval에서 에러가 발생해 프로세스가 멈추기 때문입니다. 하지만 uncaughtException 이벤트 리스너가 연결되어 있음으로 프로세스가 멈추지 않습니다.
예기치 못한 에러 Error: 서버를 고장내주마!
실행됩니다
예기치 못한 에러 Error: 서버를 고장내주마!
예기치 못한 에러 Error: 서버를 고장내주마!
예기치 못한 에러 Error: 서버를 고장내주마!
...
//계속 반복
try/catch로 처리하지 못한 에러가 발생했지만 코드가 제대로 실행되었습니다.
어떻게 보면 uncaughtException 이벤트 리스너로 모든 에러를 처리할 수 있을 것처럼 보입니다. 실제로 uncaughtException 의 콜백 함수에 에러 발생 시 복구 작업을 하는 코드를 넣어둔 경우도 본 적이 있습니다. 하지만 노드 공식 문서에서는 uncaughtException 이벤트를 최후의 수단으로 사용할 것을 명시하고 있습니다. 노드는 uncaughtException 이벤트 발생 후 다음 동작이 제대로 동작하는지를 보증하지 않습니다. 즉, 복구 작업 코드를 넣어뒀더라도 그것이 동작하는지 확신할 수 없습니다.
따라서 uncaughtException은 단순히 에러 내용을 기록하는 정도로 사용하고, 에러를 기록한 후 process.exit() 으로 프로세스를 종료하는 것이 좋습니다. 에러가 발생하는 코드를 수정하지 않는 이상, 프로세스가 실행되는 동안 에러는 계속 발생할 것입니다.
서버운영은 에러와의 싸움입니다. 모든 에러 상황에 대비하는 것이 최선이지만, 시간이나 비용, 인력 등의 제약으로 미처 대비하지 못한 에러가 발생할 수 있습니다. 따라서 에러가 발생했을 때 철저히 기록(로깅)하는 습관을 들이고, 주기적으로 로그를 확인하면서 보완해나가야 합니다.
'nodeJS' 카테고리의 다른 글
| [Node.js] Router 객체로 라우팅 분리 (0) | 2023.09.13 |
|---|---|
| [NodeJS] express (5) | 2023.09.04 |
| [Node.js] Net 모듈 (0) | 2023.08.31 |
| [Node.js] 통신(네트워크) (0) | 2023.08.30 |
| [Node.js] 내장 모듈 , require (0) | 2023.08.29 |