본문 바로가기

Node.js

[Node.js] 에러처리 올바르게 하기

노드에서는 예외처리가 정말 중요하다. 예외란 보통 처리하지 못한 에러를 말한다. 이러한 예외들은 실행 중인 노드 프로세스를 멈추게 만든다. 멀티 스레드 프로그램에서는 스레드 하나가 멈추면 그 일을 다른 스레드가 대신한다. 하지만 노드의 메인 스레드는 하나 뿐이므로 그 하나를 소중히 보호해야 한다. 메인 스레드가 에러로 인해 멈춘다는 것은 스레드르 갖고 있는 프로세스가 멈춘다는 뜻이고, 전체 서버도 멈춘다는 뜻과 같다.

 

따라서 에러를 처리하는 올바른 방법을 익혀둬야 한다.

그렇다면 에러를 올바르게 처리하는 법은 어떤 것이 있을까?

 

  • 가능한 모든 에러의 결과물에 대해서 예측 가능해야 한다.
  • 수동으로 개입하지 않아도 심각한 에러에서 스스로 복구 될 수 있어야 한다.
  • HTTP 요청을 처리하는 동안 발생한 오류는, 클라이언트가 이를 기반으로 작업을 수행하는데 도움이 될 수 있는 최소한의 정보와 함께 클라이언트에 전달되어야 한다.
  • 오류의 근본적인 원인을 쉽게 추적할 수 있고 디버깅하기도 쉬워야 한다.

1. 비동기 에러를 정확하게 잡아야합니다.

Node.js에서 비동기 오류를 처리하는 것은 강력하고 안정적인 애플리케이션을 구축하는 데 중요합니다. Node.js는 비차단형으로 설계되었으며 비동기 작업에 크게 의존하기 때문에 애플리케이션의 다양한 부분에서 오류가 발생할 수 있습니다. Node.js에서 비동기 오류를 정확하게 포착하는 방법에 대한 자세한 설명은 다음과 같습니다.

. 프로미스와 async/await:

  1. 프로미스 사용:
    • Promise는 Node.js에서 비동기 작업을 처리하는 표준 방법입니다. 비동기 함수에서 반환된 Promise가 적절하게 처리되는지 확인하세요.
someAsyncFunction()
  .then((result) => {
    // Handle success
    console.log(result);
  })
  .catch((error) => {
    // Handle error
    console.error(error);
  });

 

2. Async/Await 사용:

  • Async/Await는 Promise 작업을 위한 보다 간결하고 읽기 쉬운 구문을 제공합니다. try-catch 블록을 사용하여 오류가 포착되었는지 확인하세요.
async function example() {
  try {
    const result = await someAsyncFunction();
    // Handle success
    console.log(result);
  } catch (error) {
    // Handle error
    console.error(error);
  }
}

example()

 

3. callback 에서의 에러처리 : 오류 우선 콜백을 사용하는 이러한 패턴은 "오류 우선 콜백" 패턴으로 알려져 있으며 Node.js에서 널리 사용됩니다.

 

function myAsyncFunction(callback) {
  setTimeout(() => {
    callback(new Error('oops'))
  }, 1000)
}

myAsyncFunction((err) => {
  if (err) {
    // handle error
  } else {
    // happy path
  }
})

 

2.uncaught exception이나 unhandled rejections에 대해 적절하게 처리해야 합니다.

1.uncaught exceptionunhandled rejections처리:

Node.js 애플리케이션에서는 Promise의 try-catch 블록이나 .catch() 핸들러에서 오류가 포착되지 않으면 포착되지 않은 예외와 처리되지 않은 거부가 발생할 수 있습니다. 이러한 이벤트는 애플리케이션이 정상적인 실행을 계속할 수 없는 상황을 나타내기 때문에 중요합니다.

  • 잡히지 않은 예외(uncaughtException):
    • 이 이벤트는 애플리케이션에서 처리되지 않은 예외가 발생할 때 발생합니다.
    • 애플리케이션 충돌을 방지하고 정상적인 종료를 보장하려면 포착되지 않은 예외를 처리하는 것이 중요합니다.
  • 처리되지 않은 거부(unhandledRejection):
    • 이 이벤트는 Promise가 거부되었지만 거부를 처리할 .catch() 핸들러가 없을 때 발생합니다.
    • 처리되지 않은 거부를 올바르게 처리하는 것은 예상치 못한 동작으로 이어질 수 있는 처리되지 않은 Promise 거부를 방지하는 데 필수적입니다.

2. 적절한 취급:

이러한 이벤트에 대한 리스너를 추가할 때 애플리케이션의 안정성을 보장하기 위해 이를 적절하게 처리하는 것이 중요합니다.

  • 로깅 지우기:
    • uncaught exceptionunhandled rejections를 처리할 때 오류에 대한 자세한 정보를 기록합니다. 이 정보는 이후 분석 및 디버깅에 중요합니다.
    • 로깅에는 오류 메시지, 스택 추적, 문제의 근본 원인을 식별하는 데 도움이 되는 모든 상황별 정보 등 관련 세부 정보가 포함되어야 합니다.
  • 강제 종료:
    • 오류 로그 후, 애플리케이션을 강제 종료하는 것을 권장합니다. 이렇게 하면 프로세스 관리자 또는 Docker 조정자가 교체 프로세스를 시작할 수 있습니다.
    • 애플리케이션을 종료하면 불안정한 상태로 계속 진행되어 잠재적으로 더 많은 문제가 발생하거나 예기치 않은 동작이 발생하는 것방지할 수 있습니다.

3. 제대로 처리하지 않을 경우 발생할 수 있는 문제:

uncaught exceptionunhandled rejections가 적절하게 처리되지 않으면 다음과 같은 문제가 발생할 수 있습니다.

  • 예기치 않은 동작:
    • 이러한 중대한 오류가 발생한 후에도 애플리케이션이 계속 실행되도록 허용하면 예기치 않은 동작이 발생할 수 있습니다.
    • 애플리케이션 상태가 일관되지 않아 예측할 수 없는 결과가 발생할 수 있습니다.
  • 애플리케이션 중지 또는 충돌:
    • 적절하게 처리하지 않으면 애플리케이션이 결국 중지되거나 충돌하여 가용성과 안정성에 영향을 미칠 수 있습니다.

4. 제작 시 고려 사항:

프로덕션 환경에서는 포착되지 않은 예외와 처리되지 않은 거부를 처리하는 것이 더욱 중요합니다.

  • 로그 관리 시스템 또는 APM 서버:
    • 로그 관리 시스템이나 APM(Application Performance Monitoring) 서버와의 통합을 권장합니다.
    • 로그는 중앙 집중화되어야 모니터링, 분석, 디버깅이 쉬워집니다.
  • 프로세스 관리자 또는 Docker Orchestrator:
    • 애플리케이션을 종료하면 프로세스 관리자 또는 Docker 조정자가 오류를 감지하고 교체 프로세스를 시작할 수 있습니다.
    • 이는 애플리케이션의 전반적인 상태와 가용성을 유지하는 데 도움이 됩니다.

결론:

포착되지 않은 예외와 처리되지 않은 거부를 처리하는 것은 안정적이고 강력한 Node.js 애플리케이션을 구축하는 데 있어 중요한 측면입니다. 자세한 정보를 기록하고, 강제 종료하고, 적절한 모니터링 도구와 통합함으로써 개발자는 예상치 못한 오류가 적절하게 해결되었는지 확인하고 잠재적인 계단식 오류를 방지하며 프로덕션 환경에서 애플리케이션의 안정성을 유지할 수 있습니다.

 

3. 에러 마스킹

"오류 마스킹"의 개념은 소프트웨어 응용 프로그램의 오류, 특히 함수나 메서드 실행 중에 오류가 발생할 때 의도적으로 억제하거나 숨기는 방식을 의미합니다. 특정 수준에서 오류를 처리하고 호출 스택 위로 전파하지 않아야 하는 상황이 있을 수 있지만, 주의 깊게 고려하지 않고 맹목적으로 오류를 마스킹하면 오류 추적, 진단 및 해결의 어려움을 비롯한 여러 문제가 발생할 수 있습니다. 

1. 마스킹 오류:

  • 정의: 오류 마스킹은 함수나 코드 블록을 실행하는 동안 발생하는 오류를 의도적으로 억제하거나 적절하게 처리하지 않아 호출 코드나 응용 프로그램의 상위 수준이 오류를 인식하지 못하게 할 때 발생합니다. .

2. 일반적인 실수:

  • 블라인드 마스킹:
    • 문제: 오류의 성격이나 잠재적인 영향을 이해하지 못한 채 맹목적으로 오류를 억제하는 것은 문제가 될 수 있습니다.
    • 결과: 문제의 근본 원인을 식별하거나 문제 발생 시 문제를 진단하는 것이 어려워집니다.

3. 블라인드 오류 마스킹의 잠재적인 문제:

  • 진단 정보 손실:
    • 문제: 적절한 로깅이나 보고 없이 오류가 마스킹되면 오류 메시지, 스택 추적, 컨텍스트 등 오류에 대한 진단 정보가 손실됩니다.
    • 결과: 개발자가 무엇이 잘못되었는지 이해하는 데 필요한 정보가 부족하기 때문에 디버깅이 어려워집니다.
  • 문제를 추적할 수 없음:
    • 문제: 마스킹 오류로 인해 애플리케이션 전체에서 문제를 추적하기가 어렵습니다. 특히 즉시 명백하지 않은 경우에는 더욱 그렇습니다.
    • 결과: 개발자는 예상치 못한 결과나 애플리케이션 가동 중지 시간을 초래할 수 있는 심각한 오류를 놓칠 수 있습니다.
  • 가동 중지 시간 증가:
    • 문제: 식별되지 않고 처리되지 않은 오류시간이 지남에 따라 누적되어 가동 중지 시간이 증가하거나 성능이 저하될 수 있습니다.
    • 결과: 응용 프로그램에서 예측할 수 없는 동작이 발생할 수 있으며 중요한 문제가 해결되지 않은 상태로 남아 있을 수 있습니다.

4. 오류를 가려야 하는 경우:

오류 마스킹은 일반적으로 권장되지 않지만 필요할 수 있는 특정 시나리오가 있습니다.

  • 보안 문제:
    • 시나리오: 특정 오류, 특히 보안 취약성과 관련된 오류는 민감한 정보가 잠재적인 공격자에게 노출되지 않도록 마스킹해야 할 수 있습니다.
    • 고려사항: 보안 관련 오류를 주의 깊게 고려하고 기록하는 것이 필수적입니다.
  • 사용자 경험:
    • 시나리오: 보다 사용자 친화적인 환경을 제공하기 위해 특정 사용자 대상 시나리오의 오류를 마스킹합니다.
    • 고려 사항: 사용자 경험을 위해 오류를 마스킹하는 동안 내부 디버깅 및 모니터링을 위해 오류를 기록합니다.

5. 모범 사례:

  • 로깅 및 보고:
    • 모범 사례: 오류가 특정 수준에서 가려지더라도 항상 오류를 기록하세요.
    • 이유: 로깅은 개발자가 문제를 조사할 때 분석할 수 있는 정보 추적을 제공합니다.
  • 우아한 저하:
    • 모범 사례: 특정 오류가 발생하더라도 애플리케이션이 계속 작동할 수 있도록 점진적 성능 저하를 위한 메커니즘을 구현합니다.
    • 근거: 이는 애플리케이션이 어느 정도 작동 상태를 유지하여 더 나은 사용자 경험을 제공하도록 보장합니다.
  • 오류 내용:
    • 모범 사례: 문제 진단에 도움이 되도록 오류 메시지나 로그에 관련 상황 정보를 포함합니다.
    • 근거: 상황별 정보는 개발자가 오류로 이어지는 상황을 이해하는 데 도움이 됩니다.
  • 모니터링 및 알림:
    • 모범 사례: 가려진 오류를 감지하고 알리기 위해 모니터링 및 경고 시스템을 설정합니다.
    • 근거: 사전 감지를 통해 개발자는 문제가 에스컬레이션되기 전에 해결할 수 있습니다.

결론:

오류 마스킹은 주의 깊게 접근해야 하며 개발자는 사용자 친화적인 환경, 보안 고려 사항 및 진단 기능에 대한 요구 사항의 균형을 맞추는 신중한 전략을 채택해야 합니다. 적절한 처리 및 로깅 없이 맹목적으로 오류를 억제하면 애플리케이션을 효과적으로 유지 관리하고 문제를 해결하는 데 어려움을 겪을 수 있습니다.

 

제너릭 에러를 정확한 에러로 변환해야 합니다.

"일반적인 오류를 정확한 오류로 변환"이라는 개념은 일반적인 오류 개체오류의 성격이나 원인에 대한 추가 정보를 제공하는 보다 구체적이거나 정확한 오류 개체로 정제하거나 변환하는 관행을 의미합니다. 이는 발생한 특정 유형의 오류에 따라 애플리케이션이 다른 조치를 취해야 할 때 특히 중요합니다. 이 개념을 더 자세히 살펴보겠습니다.

일반 오류와 특정 오류:

  • 일반 오류:
    • 이는 발생한 특정 문제에 대한 자세한 정보를 제공하지 않을 수 있는 범용 오류 개체입니다.
    • 예에는 Error와 같은 내장된 JavaScript 오류 또는 특정 속성이 없는 사용자 정의 오류 개체가 포함됩니다.
  • 특정 오류:
    • 오류의 성격을 나타내는 특정 속성이나 속성을 갖는 오류 개체입니다.
    • 예에는 오류 코드, 오류 메시지 또는 특정 컨텍스트 관련 정보와 같은 추가 속성이 있는 사용자 정의 오류 클래스가 포함됩니다.

변환 오류의 중요성:

  • 작업 차별화:
    • 시나리오: 오류 유형에 따라 애플리케이션은 다른 조치를 취하거나 고유한 오류 처리 전략을 따라야 할 수도 있습니다.
    • 고려 사항: 일반 오류특정 오류로 변환하면 애플리케이션이 다양한 오류 시나리오를 구별하고 그에 따라 대응할 수 있습니다.

모범 사례:

  • 사용자 정의 오류 클래스:
    • 모범 사례: 내장된 Error 클래스를 확장하여 특정 오류 시나리오에 대한 사용자 정의 오류 클래스를 만듭니다.
    • 이유: 사용자 정의 오류 클래스를 사용하면 특정 오류 유형을 더 효과적으로 구성하고 전달할 수 있습니다.
  • 오류 메타데이터:
    • 모범 사례: 오류 코드, 컨텍스트 또는 관련 정보와 같은 특정 오류 개체에 추가 메타데이터를 포함합니다.
    • 이유: 오류 정보가 풍부할수록 디버깅 및 문제 해결에 도움이 됩니다.
  • 중앙 집중식 오류 처리:
    • 모범 사례: 일반 오류가 특정 오류로 변환되는 중앙 집중식 오류 처리 메커니즘을 구현합니다.
    • 이유: 중앙 집중식 처리는 일관성을 보장하고 중복 오류 변환 논리를 방지합니다.

결론:

일반 오류를 특정 오류로 변환하면 애플리케이션 오류 처리의 정확성과 효율성이 향상됩니다. 특정 속성을 가진 사용자 정의 오류 클래스를 도입함으로써 개발자는 다양한 오류 시나리오를 쉽게 구별하고 이에 따라 응답을 맞춤화하여 더욱 강력하고 유지 관리하기 쉬운 코드를 만들 수 있습니다.

 

외부 서비스의 예기치 못한 상황에 대처하기

만약 외부 서비스를 사용해야 하는 경우가 있다면, 가능한 잘못될 수 있는 모든 시나리오에 대처할 필요가 있다.

function processUsers() {
  try {
    const body = await client.get('http://example.com/users')
    const users = body.users || []
    // do something with users
  } catch (err) {
    // handle error
  }
}

 

위 예제에서, 사용자 목록을 불러오기 위해서는, api가 성공 응답(200)에서만 객체를 반환한다고 가정한다. 결과가 있다면 베열이 될 수도 있고, 없다면 null이 될 수도 있는 users 속성이 있다고 가정해보자.

 

만약 api 개발자들이 body.users가 외 다른 곳에서 결과가 오도록 객체의 응답구조를 변경한다면? 애플리케이션은 기본값 []를 사용하여 계속 실행되며, 어떤일이 발생했는지 알 수 없게 된다.

 

항상 다른 서비스를 사용할 때는 엄격하게 대처할 필요가 있다. 비정상적인 방법으로 계속 서비스 하는 것보다, 애플리케이션이 빠르게 실패하는 것이 좋다. 이렇게 하면 잠재적으로 발생할 수 있는 문제를 빠르게 식별할 수 있고, 데이터 손상이나 불일치 등을 막을 수 있다.

 

에러 별로 적절한 로그 레벨을 사용하기

 

적절한 로그 레벨을 선택하여 로그를 남기는 것은 중요하다. 모든 로그 라이브러리는 일반적으로 서로 다른 레벨의 로그를 기록할 수 있고, 각 수준의 로그를 다른 대상 (stdout syslog file) 으로 보낼 수 있다. 이 작업을 제대로 수행하기 위해서는, 메시지의 중요도에 따라 올바른 로그레벨을 선택해야 한다.

  • debug: 심각하지 않는 메시지. 후에 디버그를 위해서 필요한 경우
  • info: 성공(또는 실패하지 않는) 작업을 식별하는데 필요한 정보성 메시지
  • warn: 즉각적인 액션이 필요하지 않은 경고성 메시지. 그러나 추후에 디버깅을 위해서 필요한 경우
  • error: 즉각적인 액션이 필요한 모든 에러. 이 에러를 무시할 경우 심각한 시나리오로 이어지는 경우
  • fatal: 서비스 중단 과 같은 중요 구성요소의 장애를 나타내는 모든 오류
 
 
 

'Node.js' 카테고리의 다른 글

[Node.js]REST API  (0) 2024.01.02
[Node.js] HTTP, HTTPs  (0) 2024.01.02
[Node.js] 스레드 풀  (0) 2024.01.01
[Node.js] 버퍼와 스트림  (1) 2024.01.01
[Node.js] Node 내장 객체  (0) 2023.12.30