Node.js 모범 사례 예외 처리


Answers

다음은 선택한 블로그 게시물의 코드 예제 및 따옴표를 비롯하여이 주제에 대한 다양한 소스의 요약 및 큐 레이션입니다. 모범 사례의 전체 목록은 여기에서 찾을 수 있습니다.

Node.JS 오류 처리 모범 사례

Number1 : 비동기 오류 처리에 대한 약속 사용

TL : DR : 콜백 스타일의 비동기 오류 처리가 아마도 가장 빠른 지옥 (일명 운명의 피라미드) 일 것입니다. 코드에 줄 수있는 가장 좋은 선물은 try-catch와 같이 매우 작고 친숙한 코드 구문을 제공하는 신뢰할 수있는 약속 라이브러리를 사용하는 것입니다

그렇지 않으면 : Node.JS 콜백 스타일, 함수 (err, response)는 캐주얼 코드와 오류 처리, 과도한 중첩 및 어색한 코딩 패턴의 혼합으로 인해 유지 보수가 불가능한 코드로가는 유망한 방법입니다

코드 예제 - 좋은

doWork()
.then(doWork)
.then(doError)
.then(doWork)
.catch(errorHandler)
.then(verify);

코드 예제 안티 패턴 - 콜백 스타일 오류 처리

getData(someParameter, function(err, result){
    if(err != null)
      //do something like calling the given callback function and pass the error
    getMoreData(a, function(err, result){
          if(err != null)
            //do something like calling the given callback function and pass the error
        getMoreData(b, function(c){ 
                getMoreData(d, function(e){ 
                    ...
                });
            });
        });
    });
});

블로그 인용문 : "우리는 약속에 문제가 있습니다" (블로그 pouchdb에서 키워드 "노드 약속"에 대해 11 위를 차지함)

"... 콜백은 좀 더 불길한 행동을합니다. 우리는 일반적으로 프로그래밍 언어로 당연한 것으로 여기는 스택을 우리로부터 박탈합니다. 스택없이 코드를 작성하는 것은 브레이크 페달없이 자동차를 운전하는 것과 같습니다. 우리가 비동기로 갔을 때 우리가 잃어버린 언어의 기본 요소 인 되돌아 오기, 던지기, 그리고 스택을 우리에게 되돌려 주겠다는 약속의 핵심이 있습니다. 약속을 올바르게 활용하는 방법을 알아야한다. "

Number2 : 내장 된 Error 객체 만 사용하십시오.

TL : 오류를 문자열이나 사용자 정의 유형으로 던지는 코드를 보는 것이 일반적입니다 . 이는 오류 처리 논리와 모듈 간의 상호 운용성을 복잡하게합니다. 약속을 거부하거나 예외를 던지거나 오류를 발생 시키거나 - Node.JS 내장 오류 객체를 사용하면 균일 성이 향상되고 오류 정보가 손실되지 않습니다.

그렇지 않으면 : 어떤 모듈을 실행할 때 어떤 종류의 에러가 반환되는지 불확실하다 - 오는 예외에 대해 추론하고 처리하는 것을 훨씬 어렵게 만든다. 심지어 사용자 정의 유형을 사용하여 오류를 설명하면 스택 추적과 같은 치명적인 오류 정보가 손실 될 수 있습니다!

코드 예제 - 올바른 작업

    //throwing an Error from typical function, whether sync or async
 if(!productToAdd)
 throw new Error("How can I add new product when no value provided?");

//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));

//'throwing' an Error from a Promise
 return new promise(function (resolve, reject) {
 DAL.getProduct(productToAdd.id).then((existingProduct) =>{
 if(existingProduct != null)
 return reject(new Error("Why fooling us and trying to add an existing product?"));

안티 패턴 코드 예제

//throwing a String lacks any stack trace information and other important properties
if(!productToAdd)
    throw ("How can I add new product when no value provided?");

블로그 인용구 : "문자열은 오류가 아닙니다" (블로그 "devthought"에서 키워드 "Node.JS 오류 개체"에 대해 6 위)

"... 오류 대신 문자열을 전달하면 모듈 간 상호 운용성이 저하되며 instanceof 오류 검사를 수행하고 있거나 오류에 대해 더 알고 싶어하는 API와 계약이 끊어집니다 . 오류 객체는 메시지를 생성자에 전달하는 것 외에 현대 자바 스크립트 엔진에서 흥미로운 속성 "

Number3 : 작동 및 프로그래머 오류 구분

TL : 운영 오류 (예 : API가 잘못된 입력을 받음)는 오류의 영향을 완전히 이해하고 신중하게 처리 할 수있는 알려진 경우를 의미합니다. 반면에 프로그래머 오류 (예 : 정의되지 않은 변수 읽기)는 응용 프로그램을 정상적으로 다시 시작하도록 지시하는 알 수없는 코드 오류를 나타냅니다

그렇지 않은 경우 : 오류가 표시되면 항상 응용 프로그램을 다시 시작할 수 있지만 사소하고 예측 된 오류 (작동 오류)로 인해 ~ 5000 온라인 사용자가 중단되는 이유는 무엇입니까? 반대의 경우도 이상하지 않습니다. 알 수없는 문제 (프로그래머 오류)가 발생했을 때 응용 프로그램을 계속 유지하면 예기치 않은 동작이 발생할 수 있습니다. 두 가지를 구별하는 것은 현명하게 행동하고 주어진 상황에 기초한 균형 잡힌 접근법을 적용 할 수 있습니다.

코드 예제 - 올바른 작업

    //throwing an Error from typical function, whether sync or async
 if(!productToAdd)
 throw new Error("How can I add new product when no value provided?");

//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));

//'throwing' an Error from a Promise
 return new promise(function (resolve, reject) {
 DAL.getProduct(productToAdd.id).then((existingProduct) =>{
 if(existingProduct != null)
 return reject(new Error("Why fooling us and trying to add an existing product?"));

코드 예제 - 오류를 작동 가능 (신뢰할 수있는) 것으로 표시

//marking an error object as operational 
var myError = new Error("How can I add new product when no value provided?");
myError.isOperational = true;

//or if you're using some centralized error factory (see other examples at the bullet "Use only the built-in Error object")
function appError(commonType, description, isOperational) {
    Error.call(this);
    Error.captureStackTrace(this);
    this.commonType = commonType;
    this.description = description;
    this.isOperational = isOperational;
};

throw new appError(errorManagement.commonErrors.InvalidInput, "Describe here what happened", true);

//error handling code within middleware
process.on('uncaughtException', function(error) {
    if(!error.isOperational)
        process.exit(1);
});

블로그 인용문 : "그렇지 않으면 위험 할 수 있습니다."(디버깅 가능한 블로그에서 키워드 "Node.JS uncaught exception"에 대해 3 위를 차지함)

" ... JavaScript에서 throw가 작동하는 본질 상, 참조를 누설하거나 정의되지 않은 취성 상태의 다른 종류를 만들지 않고 안전하게"중단 한 부분부터 가져 오는 "방법은 거의 없습니다. 가장 안전한 방법은 다음과 같습니다. 던져진 오류는 프로세스를 종료하는 것 입니다. 물론 정상적인 웹 서버에서는 많은 연결이 열려있을 수 있으며, 다른 누군가가 오류를 유발했기 때문에 연결을 갑작스럽게 종료하는 것은 적절하지 않습니다. 오류를 유발 한 요청에 오류 응답을 보내고 나머지는 정상 시간에 끝내고 해당 작업자의 새 요청 수신을 중단합니다. "

Number4 : 미들웨어를 통하지 않고 오류를 중앙 집중식으로 처리

TL : DR에 대한 메일과 같은 오류 처리 논리는 오류가 발생할 때 모든 엔드 포인트 (예 : Express 미들웨어, cron 작업, 단위 테스트)가 호출하는 전용 집중 개체에 캡슐화되어야합니다.

그렇지 않으면 : 한 곳에서 오류를 처리하지 않으면 코드가 중복되고 부적절하게 처리되는 오류가 발생할 수 있습니다

코드 예제 - 일반적인 오류 흐름

//DAL layer, we don't handle errors here
DB.addDocument(newCustomer, (error, result) => {
    if (error)
        throw new Error("Great error explanation comes here", other useful parameters)
});

//API route code, we catch both sync and async errors and forward to the middleware
try {
    customerService.addNew(req.body).then(function (result) {
        res.status(200).json(result);
    }).catch((error) => {
        next(error)
    });
}
catch (error) {
    next(error);
}

//Error handling middleware, we delegate the handling to the centrzlied error handler
app.use(function (err, req, res, next) {
    errorHandler.handleError(err).then((isOperationalError) => {
        if (!isOperationalError)
            next(err);
    });
});

블로그 인용구 : "때로는 하위 수준에서 호출자에게 오류를 전파하는 것 외에는 아무 것도 할 수 없습니다"(Joyent 블로그에서 "Node.JS 오류 처리"키워드에 대해 1 위를 차지함)

"... 스택의 여러 레벨에서 같은 오류를 처리하게 될 수도 있습니다. 이것은 하위 레벨이 호출자에게 오류를 전파하고 호출자에게 오류를 전파하는 것을 제외하고는 아무 것도 할 수없는 경우에 발생합니다. 최상위 호출자 만 적절한 응답이 무엇인지, 작업을 다시 시도할지, 사용자에게 오류를보고할지 또는 다른 어떤 것인지를 알 수 있습니다. 그러나 모든 오류를 단일 최상위 수준에보고해야한다는 것을 의미하지는 않습니다 콜백은 그 콜백 자체가 어떤 컨텍스트에서 에러가 발생했는지 알 수 없기 때문에 "

Number5 : Swagger를 사용하여 문서 API 오류 발생

TL : DR : API 호출자가 어떤 오류가 발생했는지 알 수 있도록하여 충돌없이 신중하게 처리 할 수 ​​있도록합니다. 이것은 일반적으로 Swagger와 같은 REST API 문서 프레임 워크로 수행됩니다.

그렇지 않으면 API 클라이언트가 이해할 수없는 오류를 다시 수신했기 때문에 충돌하고 다시 시작하기로 결정할 수 있습니다. 참고 : 귀하의 API를 호출 한 사람이 귀하 일 수 있습니다 (마이크로 서비스 환경에서는 매우 일반적 임)

블로그 인용문 : "발신자에게 어떤 오류가 발생할 수 있는지 알려줘야합니다."(Joyent 블로그에서 "Node.JS logging"키워드에 대해 1 위를 차지했습니다)

... 오류를 처리하는 방법에 대해 이야기했지만 새로운 함수를 작성할 때 함수를 호출 한 코드에 오류를 어떻게 전달합니까? ... 어떤 오류가 발생할 수 있는지 또는 그 의미가 무엇인지 모르는 경우 우연히 만 프로그램을 수정할 수 있습니다. 따라서 새로운 기능을 작성하는 경우 발신자에게 어떤 오류가 발생할 수 있는지, 어떤 오류가 발생할 수 있는지 알려 주어야합니다

넘버 6 : 낯선 사람이 마을에 올 때 공정을 정상적으로 중단하십시오.

TL : DR : 알 수없는 오류가 발생하면 (개발자 오류, 모범 사례 번호 3 참조) 응용 프로그램의 건강 상태에 대한 불확실성이 있습니다. 일반적인 방법은 Forever 및 PM2와 같은 'restarter'도구를 사용하여 신중하게 프로세스를 다시 시작하는 것입니다.

그렇지 않은 경우 : 익숙하지 않은 예외가 발견되면 일부 객체가 오류 상태 (예 : 전역 적으로 사용되며 일부 내부 오류로 인해 이벤트를 더 이상 발생시키지 않는 이벤트 이미 터)에있을 수 있으며 이후의 모든 요청이 실패하거나 미친 듯 행동 할 수 있습니다

코드 예제 - 크래시 할 것인지 결정

//deciding whether to crash when an uncaught exception arrives
//Assuming developers mark known operational errors with error.isOperational=true, read best practice #3
process.on('uncaughtException', function(error) {
 errorManagement.handler.handleError(error);
 if(!errorManagement.handler.isTrustedError(error))
 process.exit(1)
});


//centralized error handler encapsulates error-handling related logic 
function errorHandler(){
 this.handleError = function (error) {
 return logger.logError(err).then(sendMailToAdminIfCritical).then(saveInOpsQueueIfCritical).then(determineIfOperationalError);
 }

 this.isTrustedError = function(error)
 {
 return error.isOperational;
 }

블로그 인용구 : "오류 처리에 관한 생각이 세 학교 ​​있습니다"(블로그 jsrecipes에서)

오류 처리에 대한 생각은 주로 다음 세 가지입니다. 1. 응용 프로그램을 충돌시키고 다시 시작하십시오. 2. 가능한 모든 오류를 처리하고 절대로 충돌하지 마십시오. 3. 둘 사이의 균형 잡힌 접근

Number7 : 성숙한 로거를 사용하여 오류 가시성 향상

TL : DR : Winston, Bunyan 또는 Log4J와 같은 성숙한 로깅 도구 모음은 오류 발견과 이해를 가속화합니다. 그래서 console.log를 잊어 버리십시오.

그렇지 않으면 : console.logs를 건너 뛰거나 도구 나 괜찮은 로그 뷰어를 사용하지 않고 지저분한 텍스트 파일을 통해 수동으로 늦게까지 작업 할 때 바쁠 수도 있습니다

코드 예제 - Winston logger in action

//your centralized logger object
var logger = new winston.Logger({
 level: 'info',
 transports: [
 new (winston.transports.Console)(),
 new (winston.transports.File)({ filename: 'somefile.log' })
 ]
 });

//custom code somewhere using the logger
logger.log('info', 'Test Log Message with some parameter %s', 'some parameter', { anything: 'This is metadata' });

블로그 인용구 : "몇 가지 요구 사항을 파악할 수 있습니다 (로거 용) :"(블로그 strongblog에서)

... 몇 가지 요구 사항을 식별 할 수 있습니다 (로거 용). 1. 각 로그 라인에 시간 소인을 찍으십시오. 이것은 자명하다. 각 로그 항목이 언제 발생했는지 알 수 있어야한다. 2. 로깅 형식은 기계뿐만 아니라 사람이 쉽게 소화 할 수 있어야합니다. 3. 구성 가능한 여러 대상 스트림을 허용합니다. 예를 들어, 추적 로그를 하나의 파일에 기록 할 수 있지만 오류가 발생하면 동일한 파일에 기록한 다음 오류 파일에 기록하고 동시에 이메일을 보내십시오.

Number8 : APM 제품을 사용하여 오류 및 가동 중지 시간 발견

TL : 모니터링 및 성능 제품 (일명 APM)은 코드베이스 또는 API를 사전에 측정하여 누락 된 오류, 충돌 및 느린 부분을 자동으로 강조 표시합니다.

그렇지 않은 경우 : API 성능 및 가동 중지 시간을 측정하는 데 많은 노력을 기울일 수 있습니다 . 실제 시나리오에서 가장 느린 코드 부분과 UX에 미치는 영향을 알지 못할 것입니다

블로그 인용구 : "APM 제품 세그먼트"(Yoni Goldberg 블로그에서)

"... APM 제품은 3 가지 주요 부분으로 구성됩니다 : 1. 웹 사이트 또는 API 모니터링 - HTTP 요청을 통해 가동 시간과 성능을 지속적으로 모니터링하는 외부 서비스 몇 분 안에 설정 가능 다음은 Pingdom, 가동 시간 로봇 및 New Relic 2 코드 계측 - 느린 코드 감지, 예외 통계, 성능 모니터링 등의 혜택을 누리기 위해 애플리케이션 내에 에이전트를 내장해야하는 제품군 - New Relic, App Dynamics 3. Operational Intelligence Dashboard - 이러한 라인 제품의 여러 가지 정보 (응용 프로그램 로그, DB 로그, 서버 로그 등)를 집계하고 선행 대시 보드 디자인을 포함하는 응용 프로그램 성능을 쉽게 유지하는 데 도움이되는 메트릭 및 큐레이팅 된 콘텐츠로 운영 팀을 지원하는 데 중점을 둡니다. 다음은 몇 가지 경쟁자입니다 : Datadog, Splunk "

위의 내용은 단축 된 버전입니다 - 모범 사례와 예제에 대한 자세한 내용은 여기를 참조하십시오.

Question

며칠 전에 node.js를 시험해보기 시작했습니다. 내 프로그램에서 처리되지 않은 예외가있을 때마다 Node가 종료된다는 것을 알았습니다. 이는 처리되지 않은 예외가 발생하고 컨테이너가 여전히 요청을 수신 할 수있을 때 Worker Thread 만 죽는 곳에 노출 된 일반 서버 컨테이너와 다릅니다. 이것은 몇 가지 질문을 제기한다 :

  • process.on('uncaughtException') 만이이를 보호하는 효과적인 방법입니까?
  • process.on('uncaughtException') 은 비동기 프로세스 실행 중에 처리되지 않은 예외를 catch합니까?
  • 잡히지 않은 예외의 경우에 활용할 수있는 이미 구축 된 모듈 (예 : 전자 메일 보내기 또는 파일에 쓰기)이 있습니까?

node.js에서 캐치되지 않은 예외를 처리하는 일반적인 최선의 방법을 보여줄 포인터 / 아티클에 감사드립니다.




After reading this post some time ago I was wondering if it was safe to use domains for exception handling on an api / function level. I wanted to use them to simplify exception handling code in each async function I wrote. My concern was that using a new domain for each function would introduce significant overhead. My homework seems to indicate that there is minimal overhead and that performance is actually better with domains than with try catch in some situations.

http://www.lighthouselogic.com/#/using-a-new-domain-for-each-async-function-in-node/




Catching errors has been very well discussed here, but it's worth remembering to log the errors out somewhere so you can view them and fix stuff up.

​Bunyan is a popular logging framework for NodeJS - it supporst writing out to a bunch of different output places which makes it useful for local debugging, as long as you avoid console.log. ​ In your domain's error handler you could spit the error out to a log file.

var log = bunyan.createLogger({
  name: 'myapp',
  streams: [
    {
      level: 'error',
      path: '/var/tmp/myapp-error.log'  // log ERROR to this file
    }
  ]
});

검사 할 오류 및 / 또는 서버가 많으면 시간이 오래 걸릴 수 있으므로 Raygun (면책 조항, Raygun에서 작업) 도구를 사용하여 오류를 그룹화하거나 함께 사용하는 것이 좋습니다. Raygun을 도구로 사용하기로 결정했다면 설정하기가 매우 쉽습니다.

var raygunClient = new raygun.Client().init({ apiKey: 'your API key' });
raygunClient.send(theError);

PM2와 같은 도구를 사용하거나 영원히 교차하면 앱이 충돌하고 로그 아웃 할 수 있어야하며 중요한 문제없이 재부팅 할 수 있어야합니다.




nodejs 도메인nodej 에서 오류를 처리하는 가장 최근의 방법입니다. 도메인은 오류 / 기타 이벤트는 물론 전통적으로 발생하는 객체를 캡처 할 수 있습니다. 또한 도메인은 인터셉트 메소드를 통해 첫 번째 인수로 전달 된 오류로 콜백을 처리하는 기능을 제공합니다.

일반적인 try / catch 스타일의 오류 처리와 마찬가지로 일반적으로 오류가 발생하면 오류를 발생시키는 것이 가장 좋으며 오류를 격리하려는 영역을 차단하여 나머지 코드에 영향을주지 않는 것이 좋습니다. 이 영역을 "차단"하는 방법은 격리 된 코드 블록과 같은 기능을 사용하여 domain.run을 호출하는 것입니다.

동기 코드에서 위의 내용만으로도 충분합니다. 오류가 발생하면 오류가 발생하거나 되돌릴 필요가있는 데이터를 되돌리고 처리합니다.

try {  
  //something
} catch(e) {
  // handle data reversion
  // probably log too
}

비동기 콜백에서 오류가 발생하면 데이터 롤백 (공유 상태, 데이터베이스와 같은 외부 데이터 등)을 완벽하게 처리 할 수 ​​있어야합니다. 또는 예외가 발생했음을 나타 내기 위해 무언가를 설정해야합니다. 플래그가 마음에 들지 않으면 콜백이 완료 될 때까지 기다려야합니다.

var err = null;
var d = require('domain').create();
d.on('error', function(e) {
  err = e;
  // any additional error handling
}
d.run(function() { Fiber(function() {
  // do stuff
  var future = somethingAsynchronous();
  // more stuff

  future.wait(); // here we care about the error
  if(err != null) {
    // handle data reversion
    // probably log too
  }

})});

위의 코드 중 일부는 추악하지만 직접 만들 수있는 패턴을 만들 수 있습니다 (예 :

var specialDomain = specialDomain(function() {
  // do stuff
  var future = somethingAsynchronous();
  // more stuff

  future.wait(); // here we care about the error
  if(specialDomain.error()) {
    // handle data reversion
    // probably log too
  } 
}, function() { // "catch"
  // any additional error handling
});

업데이트 (2013-09) :

앞에서 나는 섬유 의미 를 암시하는 미래를 사용하는데, 이는 당신이 미래의 인라인을 기다릴 수있게한다. 이것은 실제로 당신이 모든 것을 위해 전통적인 try-catch 블록을 사용할 수있게 해줍니다. 나는 이것이 최선의 방법이라고 생각합니다. 그러나, 당신은 항상 이것을 할 수 없다 (예 : 브라우저에서) ...

파이버 의미론을 필요로하지 않는 미래도 있습니다 (보통의 브라우저 자바 스크립트에서 작동합니다). 이것들은 선물, 약속 또는 연기라고 할 수 있습니다 (여기에서 나는 미래에 대해서만 언급 할 것입니다). Plain-old-JavaScript 미래 라이브러리는 오류가 미래간에 전파 될 수 있도록합니다. 이러한 라이브러리 중 일부만 올바르게 던져진 미래를 허용하므로주의하십시오.

예 :

returnsAFuture().then(function() {
  console.log('1')
  return doSomething() // also returns a future

}).then(function() {
  console.log('2')
  throw Error("oops an error was thrown")

}).then(function() {
  console.log('3')

}).catch(function(exception) {
  console.log('handler')
  // handle the exception
}).done()

이것은 조각이 비동기 임에도 불구하고 일반적인 try-catch를 모방 한 것입니다. 그것은 인쇄 할 것입니다 :

1
2
handler

그 흐름을 방해하는 예외가 발생했기 때문에 '3'이 인쇄되지 않습니다.

블루 버드 약속에 대해 살펴보십시오.

던져진 예외를 적절히 처리하는 다른 라이브러리가 많이 있습니다. 예를 들어, jQuery는 지연됩니다. "실패한"핸들러는 예외 처리기를 던지지 않을 것입니다. 제 생각에는 처리기입니다.




try-catch를 사용하는 것이 적절한 경우는 ForEach 루프를 사용할 때입니다. 동기식이지만 동시에 내부 범위에서 return 문을 사용할 수 없습니다. 대신 try and catch 방식을 사용하여 적절한 범위에서 Error 객체를 반환 할 수 있습니다. 중히 여기다:

function processArray() {
    try { 
       [1, 2, 3].forEach(function() { throw new Error('exception'); }); 
    } catch (e) { 
       return e; 
    }
}

위의 @balupton에서 설명한 방법의 조합입니다.




Related